【问题标题】:Rust box a single trait that encompasses generic traitsRust 将包含通用特征的单个特征框起来
【发布时间】:2021-03-07 05:34:52
【问题描述】:

我的情况是,我希望一些对象实现一个特征,比如“Base”,而其他一些对象将实现一个特征“Super”。 Super trait 也必须对T : Base 是通用的,这样我就可以根据它所专门使用的 Base 自动实现 Super 的某些部分。现在这似乎适用于以下简单示例

trait Base {
    fn say_hi() -> &'static str;
}

struct BaseOne {}
struct BaseTwo {}

impl Base for BaseOne {
    fn say_hi() -> &'static str {
        "hi!"
    }
}

impl Base for BaseTwo {
    fn say_hi() -> &'static str {
        "hello!"
    }
}

trait Super<T: Base> {
    fn say_hi(&self) -> &'static str {
        T::say_hi()
    }
}

struct SuperOne;
struct SuperTwo;

impl Super<BaseOne> for SuperOne {}
impl Super<BaseTwo> for SuperTwo {}

我的问题来自我的下一个要求,即我希望能够存储实现 Super 的对象向量,而不管它专门用于哪个 Base。我的想法是创建一个涵盖所有 Supers 的特征,例如下面的 AnySuper 特征

trait AnySuper {
    fn say_hi(&self) -> &'static str;
}

impl<T> AnySuper for dyn Super<T> where T : Base {
    fn say_hi(&self) -> &'static str {
        Super::say_hi(self)
    }
}

然后存储一个 Box 的向量,如下例所示

fn main() {
    let one = Box::new(SuperOne);
    let two = Box::new(SuperTwo);
    
    let my_vec: Vec<Box<dyn AnySuper>> = Vec::new();
    
    my_vec.push(one);
    my_vec.push(two);
}

但不幸的是,失败并出现以下错误

error[E0277]: the trait bound `SuperOne: AnySuper` is not satisfied
  --> src/main.rs:52:17
   |
52 |     my_vec.push(one);
   |                 ^^^ the trait `AnySuper` is not implemented for `SuperOne`
   |
   = note: required for the cast to the object type `dyn AnySuper`

error[E0277]: the trait bound `SuperTwo: AnySuper` is not satisfied
  --> src/main.rs:53:17
   |
53 |     my_vec.push(two);
   |                 ^^^ the trait `AnySuper` is not implemented for `SuperTwo`
   |
   = note: required for the cast to the object type `dyn AnySuper`

这有点奇怪,因为在我看来,我已经为所有 Super&lt;T&gt; 实现了 AnySuper。所以我的问题是,我是在做一些根本错误的事情还是只是我的语法有问题?

附:如果有人想玩,我已经在https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=a54e3e9044f1edaeb24d8ad934eaf7ec 建立了一个带有此代码的游乐场。

【问题讨论】:

    标签: rust


    【解决方案1】:

    我认为发生了一些奇怪的事情,因为您正在经历两层特征对象间接,即从具体的Box&lt;SuperOne&gt;Box&lt;dyn Super&lt;BaseOne&gt;&gt;Box&lt;dyn AnySuper&gt;。当然,Rust 可以处理第一种情况,我们一直都在这样做,但第二种情况我从未见过。

    不过,从它的声音来看,您想说“AnySuper 是在任何Super 用于任何T 时实现的”,而您在代码中编写的是“AnySuper 是为任何AnySuper 实现的这个有趣的特征对象类型称为dyn Super&lt;T&gt;"。让我们尝试编写一个实际的一揽子实现。

    impl<S, T> AnySuper for S where S : Super<T>, T : Base
    

    但现在我们收到了一些关于无约束类型的相当令人兴奋的错误消息。

    error[E0207]: the type parameter `T` is not constrained by the impl trait, self type, or predicates
      --> src/main.rs:36:9
       |
    36 | impl<S, T> AnySuper for S where S : Super<T>, T : Base {
       |         ^ unconstrained type parameter
    

    您可以在 the relevant RFC 上阅读有关此错误动机的更多信息,但最重要的是,对于某些任意的 S,Rust 无法确定 T 应该是什么。

    这通常是正确的。如果你给 Rust 一些实现Super&lt;T&gt; 的任意东西,它可能会为几个不同的T 实现Super&lt;T&gt;,然后AnySuper 将不得不在它们之间进行选择,这是它无法做到的。相反,我们需要向 Rust 保证,对于给定的实现者,只有一个可能的 T,我们可以通过将 T 设为 associated type 来做到这一点。

    trait Super {
        type T : Base;
        fn say_hi(&self) -> &'static str {
            Self::T::say_hi()
        }
    }
    
    impl Super for SuperOne {
        type T = BaseOne;
    }
    impl Super for SuperTwo {
        type T = BaseTwo;
    }
    
    impl<S> AnySuper for S where S : Super, S::T : Base {
        fn say_hi(&self) -> &'static str {
            Super::say_hi(self)
        }
    }
    

    现在 Rust 会很乐意接受您的 AnySuper 向量。

    Rust Playground link

    工作代码示例:

    trait Base {
        fn say_hi() -> &'static str;
    }
    
    struct BaseOne {}
    struct BaseTwo {}
    
    impl Base for BaseOne {
        fn say_hi() -> &'static str {
            "hi!"
        }
    }
    
    impl Base for BaseTwo {
        fn say_hi() -> &'static str {
            "hello!"
        }
    }
    
    trait Super {
        type T : Base;
        fn say_hi(&self) -> &'static str {
            Self::T::say_hi()
        }
    }
    
    struct SuperOne;
    struct SuperTwo;
    
    impl Super for SuperOne {
        type T = BaseOne;
    }
    impl Super for SuperTwo {
        type T = BaseTwo;
    }
    
    trait AnySuper {
        fn say_hi(&self) -> &'static str;
    }
    
    impl<S> AnySuper for S where S : Super, S::T : Base {
        fn say_hi(&self) -> &'static str {
            Super::say_hi(self)
        }
    }
    
    fn main() {
        let one = Box::new(SuperOne);
        let two = Box::new(SuperTwo);
        
        let mut my_vec: Vec<Box<dyn AnySuper>> = Vec::new();
        
        my_vec.push(one);
        my_vec.push(two);
        
        println!("Success!");
    }
    

    【讨论】:

    • 谢谢,这个答案太棒了!在侧节点上,即使两个特征具有相同的签名,也没有一种简单的方法可以自动将所有函数调用从 AnySuper 映射到 Super,对吧?
    • 正确。尽管这两个 say_hi 函数共享一个名称和签名,但就 Rust 而言,它们是不相关的,因为它们存在于不同的特征中。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2022-08-03
    • 1970-01-01
    • 2017-05-23
    • 2017-10-28
    • 2018-07-12
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多