rust 当你的trait不是对象安全的时候,dyn有什么替代品吗?

yc0p9oo0  于 11个月前  发布在  其他
关注(0)|答案(1)|浏览(98)

我想做一个程序,使用命令行参数来选择使用的结构。类似于这样:

trait Animal {
    fn sound(&self) -> &str;
}

struct Dog {}

impl Animal for Dog {
    fn sound(&self) -> &str {
        "Woof!"
    }
}

struct Cat {}

impl Animal for Cat {
    fn sound(&self) -> &str {
        "Miau!"
    }
}

trait Place {
    fn name(&self) -> &str;
}

struct Museum {}

impl Place for Museum {
    fn name(&self) -> &str {
        "museum"
    }
}

struct Hotel{}

impl Place for Hotel {
    fn name(&self) -> &str {
        "hotel"
    }
}

fn get_animal(animal: &str) -> Box<dyn Animal> {
    match animal {
        "dog" => Box::new(Dog{}),
        "cat" => Box::new(Cat{}),
        _ => todo!()
    }
}

fn get_place(place: &str) -> Box<dyn Place> {
    match place {
        "museum" => Box::new(Museum{}),
        "hotel" => Box::new(Hotel{}),
        _ => todo!()
    }
}

fn sentence(animal: Box<dyn Animal>, place: Box<dyn Place>) {
    println!("{} We are at {}.", animal.sound(), place.name());
}

fn main() {
    let args: Vec<String> = env::args().collect();

    let animal = get_animal(&args[1]);
    let place = get_place(&args[2]);
    
    sentence(animal, place);
}

字符串
然而,我使用的traits不是对象安全的,所以我不能使用dyn。现在我只是对每个组合使用match

match (animal, place) {
    ("dog", "museum") => sentence(Dog{}, Museum{}),
    ("dog", "hotel") => sentence(Dog{}, Hotel{}),
    ("cat", "museum") => sentence(Cat{}, Museum{}),
    ("cat", "hotel") => sentence(Cat{}, Hotel{}),
}


然而,我将在程序中添加更多的结构体,所以这是不可伸缩的。有没有什么可伸缩的方法来解决这个问题?有没有可能创建一个宏来生成match,这样我就不必手动编写它,或者有没有更好的解决方案?
我使用的特征是:

修改traits使其成为对象安全的可能不是一个好主意,原因有两个:
1.这些库执行复杂的数学算法,不容易理解。
1.该程序用于对库进行基准测试。也许修改特性可以改变库的性能。

dphi5xsq

dphi5xsq1#

基本上有两个原因可以解释为什么trait不是对象安全的:

  • 因为它包括通用函数,
  • 和/或因为它包含一个没有接收器的函数(即它没有self参数)。

如果你需要调用一个这样的trait方法,那么你将需要使用match,就像你正在做的(*)一样。这可以通过使用crate(例如enum_dispatch)来更加符合人体工程学。
如果你需要调用的方法都是对象安全的,你可以创建一个 Package 器trait,它只包含对象安全的方法:

use std::fmt::Debug;

trait NotObjectSafe {
    fn generic_method<T: Debug> (&self, val: &T);
    fn static_method();
    fn object_safe_method (&self);
}

impl NotObjectSafe for i32 {
    fn generic_method<T: Debug> (&self, val: &T) {
        println!("Generic method for {self}: {val:?}");
    }
    
    fn static_method() {
        println!("Static method for i32");
    }
    
    fn object_safe_method (&self) {
        println!("Object safe method for {self}");
    }
}

trait ObjectSafeWrapper {
    fn object_safe_method (&self);
}

impl<T: NotObjectSafe> ObjectSafeWrapper for T {
    fn object_safe_method (&self) {
        NotObjectSafe::object_safe_method (self)
    }
}

fn main() {
    let i = 42;
    let r: &dyn ObjectSafeWrapper = &i;
    r.object_safe_method();
}

字符串
Playground
(*)如果你需要访问有限数量的泛型方法,你仍然可以制作一个 Package trait,但是你需要为你计划使用的类型提供非泛型版本的泛型方法:用途:

use std::fmt::Debug;

trait NotObjectSafe {
    fn generic_method<T: Debug> (&self, val: &T);
}

impl NotObjectSafe for i32 {
    fn generic_method<T: Debug> (&self, val: &T) {
        println!("Generic method for {self}: {val:?}");
    }
}

trait ObjectSafeWrapper {
    fn generic_method_on_f32 (&self, val: &f32);
}

impl<T: NotObjectSafe> ObjectSafeWrapper for T {
    fn generic_method_on_f32 (&self, val: &f32) {
        NotObjectSafe::generic_method (self, val)
    }
}

fn main() {
    let i = 42;
    let r: &dyn ObjectSafeWrapper = &i;
    r.generic_method_on_f32 (&3.14);
}


Playground

相关问题