当你的特质不是对象安全时,有什么替代 dyn 的方法吗?

问题描述 投票:0回答:1

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

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);
}

但是,我使用的特征不是对象安全的,所以我不能使用

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
的宏,这样我就不必手动编写它,或者是否有更好的解决方案?

我正在使用的特征是:

修改特征以使其对象安全可能不是一个好主意,原因有两个:

  1. 该库执行不容易理解的复杂数学算法。
  2. 该程序用于对库进行基准测试。也许修改特征可以改变库的性能。
rust command-line-interface
1个回答
0
投票

特质不是对象安全的,基本上有两个原因:

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

如果您需要调用这样的特征方法,那么您将需要使用

match
,就像您正在做的 (*) 一样。通过使用板条箱,例如箱子,可以使这更符合人体工程学。
enum_dispatch
.

如果您需要调用的方法都是对象安全的,则可以使用仅包含对象安全方法的总体实现来创建包装器特征:

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();
}

游乐场


(*) 如果您需要访问有限数量的泛型方法,您仍然可以创建包装器特征,但您需要为您计划使用的类型提供泛型方法的非泛型版本:

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);
}

游乐场

© www.soinside.com 2019 - 2024. All rights reserved.