具有默认特征实现的 Rust 泛型

Rust Generics with Default Trait Implementations

提问人:Alberto 提问时间:11/9/2023 更新时间:11/11/2023 访问量:81

问:

我正在做一个 Rust 项目,我定义了几个特征(A1、A2 和 A3),每个特征都有多个实现。我还有一个结构算法,它基于这些特征采用泛型。我想为泛型提供默认特征实现,以防用户未提供特定实现。

这是我的代码:

pub trait A1 {
    fn test1();
}

#[derive(Default)]
pub struct Ex1_A1;

impl A1 for Ex1_A1 {
    fn test1() {
        println!("Ex1_A1");
    }
}

pub struct Ex2_A1;
impl A1 for Ex2_A1 {
    fn test1() {
        println!("Ex2_A1");
    }
}

pub trait A2 {
    fn test2();
}

pub struct Ex1_A2;
impl A2 for Ex1_A2 {
    fn test2() {
        println!("Ex1_A2");
    }
}

pub trait A3 {
    fn test3();
}

pub struct Ex1_A3;
impl A3 for Ex1_A3 {
    fn test3() {
        println!("Ex1_A3");
    }
}

struct Algo<B: A1, S: A2, M: A3> {
    a1: B,
    a2: S,
    a3: M
} 

impl <B: A1, S: A2, M: A3> Algo<B, S, M> {
    pub fn new(bbs: Option<B>, su: Option<S>, mac: Option<M>) -> Self {
        let bbs = bbs.unwrap_or(Ex1_A1);
        let su = su.unwrap_or(Ex1_A2);
        let mac = mac.unwrap_or(Ex1_A3);
        Self {
            a1: bbs,
            a2: su,
            a3: mac,
        }
    }
}

我的问题是,如何为 Algo 结构中的泛型 B、S 和 M 提供默认实现?我尝试使用 Option 和 unwrap_or,但它似乎没有按预期工作。

我将不胜感激任何关于如何在 Rust 中有效处理这种情况的指导或示例。

谢谢!

仿制药 蚀性状

评论

2赞 Unlikus 11/9/2023
您可以通过 Default 特征获取默认值。您可以设置如下默认类型:Algo<B = Ex1_A1, S =Ex2_A2, M = Ex1_A3> 如果需要,用户可以覆盖。

答:

-1赞 Chayim Friedman 11/9/2023 #1

编译时值(泛型参数的类型)不能依赖于运行时值 ()。所以你不能那样做。Option

评论

0赞 Alberto 11/9/2023
那么,我怎样才能实现类似的东西呢?
0赞 Chayim Friedman 11/10/2023
@Alberto 每个默认值都可以有一个方法。或者你可以放弃你的想法,要求你的用户指定默认值。还不错。
0赞 user4815162342 11/9/2023 #2

提供默认值不能很好地与静态调度相吻合,因为泛型实现和默认实现的类型不同,因此无法使用明确的类型初始化字段。Algo

解决此问题的直接方法是使用 键入擦除字段。尽管动态调度由于它引入了间接性而有不好的说唱,但当用作算法的网关时,它确实非常好。例如,您可以这样定义:Algodyn ...Algo

struct Algo {
    a1: Box<dyn A1>,
    a2: Box<dyn A2>,
    a3: Box<dyn A3>,
}

impl Algo {
    pub fn new(
        bbs: Option<Box<dyn A1>>,
        su: Option<Box<dyn A2>>,
        mac: Option<Box<dyn A3>>,
    ) -> Self {
        Self {
            a1: bbs.unwrap_or_else(|| Box::new(Ex1_A1)),
            a2: su.unwrap_or_else(|| Box::new(Ex1_A2)),
            a3: mac.unwrap_or_else(|| Box::new(Ex1_A3)),
        }
    }
}

为此,您的特征方法需要采取:&self

pub trait A1 {
    fn test1(&self);
}

#[derive(Default)]
pub struct Ex1_A1;

impl A1 for Ex1_A1 {
    fn test1(&self) {
        println!("Ex1_A1");
    }
}
// ...

最后,现在使用 trait 对象调用:new()

fn main() {
    let algo2 = Algo::new(Some(Box::new(Ex1_A1)), None, None);
}

操场

评论

0赞 Alberto 11/9/2023
抱歉,这现在正在工作,当我尝试使用 new(None, None, None) 创建 Algo 实例时,它说需要类型注释
0赞 user4815162342 11/10/2023
@Alberto更新了答案,请看一下。
0赞 Alberto 11/10/2023
谢谢,你能帮我再走一步吗?我希望有另一个这样的结构: #[derive(Serialize, Deserialize)] struct Principal { //...其他属性 #[serde(skip_serializing)] alg: Algo } 但它是这样说的:“不满足绑定的特征”Algo: Deserialize<'_>
0赞 user4815162342 11/10/2023
@Alberto我认为你需要,并实现.操场#[serde(skip)]Default for Algo
0赞 user4815162342 11/18/2023
@Alberto 我的回答对您的问题有帮助吗?如果是这样,请考虑接受它。
-1赞 Dmitry 11/9/2023 #3

首先,您需要将特征边界添加到:Defaultimpl

impl<B, S, M> Algo<B, S, M> 
where
    B: A1 + Default,
    S: A2 + Default,
    M: A3 + Default
{
    pub fn new(bbs: Option<B>, su: Option<S>, mac: Option<M>) -> Self {
        let bbs = bbs.unwrap_or_default();
        let su = su.unwrap_or_default();
        let mac = mac.unwrap_or_default();
        Self {
            a1: bbs,
            a2: su,
            a3: mac,
        }
    }
}

提高代码可用性的下一步是为泛型设置默认的具体类型:Algo

struct Algo<B: A1 = Ex1_A1, S: A2 = Ex1_A2, M: A3 = Ex1_A3> {
    a1: B,
    a2: S,
    a3: M
}

// Now `Algo` without explicit generics definition means just `Algo<Ex1_A1, Ex1_A2, Ex1_A3>`

最后,在这里使用构建器模式要好得多:

impl Algo {
    // Actually you can simply derive `Default` for the `Algo` instead of explicitly defining `new` method
    // Writing this only for better understanding
    pub fn new() -> Self
    {
        Self {
            a1: Default::default(),
            a2: Ex1_A2,
            a3: Ex1_A3,
        }
    }
}

impl<B, S, M> Algo<B, S, M> 
where
    B: A1,
    S: A2,
    M: A3,
{
    pub fn with_a1<NEW_B: A1>(self, a1: NEW_B) -> Algo<NEW_B, S, M> {
        let Algo { a2, a3, .. } = self;
        Algo { a1, a2, a3 }
    }

    pub fn with_a2<NEW_S: A2>(self, a2: NEW_S) -> Algo<B, NEW_S, M> {
        let Algo { a1, a3, .. } = self;
        Algo { a1, a2, a3 }
    }

    pub fn with_a3<NEW_M: A3>(self, a3: NEW_M) -> Algo<B, S, NEW_M> {
        let Algo { a1, a2, .. } = self;
        Algo { a1, a2, a3 }
    }
}

现在,您可以根据需要管理您的构造:Algo

let my_algo = Algo::new()
    .with_a1(Ex2_A1)
    .with_a3(Ex1_A3);

评论

0赞 Alberto 11/9/2023
对不起,但这现在正在工作,编译器说“需要类型注释”
0赞 Dmitry 11/11/2023
添加了一些调整。工作正常 play.rust-lang.org/...
0赞 user4815162342 11/11/2023
我认为这实现了与 OP 需要的东西不同的东西。您的第一个代码片段需要实现的特征,并且还需要实现 - 但这不是 OP 想要的,他们希望在选项为 None 时使用不同的实现,而不是使用实现类型的默认配置。你的构建器模式也不起作用,因为 returns ,所以你唯一可以调用的是 。A1A2A3DefaultAlgo::new()Algo<Ex1_A1, Ex1_A2, Ex1_A3>with_a1()Ex2_A1
0赞 Dmitry 11/12/2023
“当选项为”无时,不同的实现“是什么意思?