实现一个类型对于实现 Trait 的借用<Trait>。

9

假设我有一些特性:

trait MyTrait {
    fn function1(&self);
}

和实现它的某种类型:

struct MyStruct {
    number: i32,
}
impl MyTrait for MyStruct {
    fn function1(&self) {
        printn!("{}", self.number);
    }
}

现在我有另一种类型,想要使用实现 MyTrait 的物品。它不关心它们是否被拥有。从阅读周围的内容来看,似乎实现这个的正确方法是让它取代 X&X,或任何其他东西,接受 Borrow<X>。这使得它可以获取 XRc<X>Box<X> 等类型的物品。

X 是一个具体类型时,我已经做到了这一点,但当 X 是一个 trait 时,该怎么办呢?

以下是我首先尝试的:

struct Consumer<T> {
    inner: T
}

impl<T: Borrow<MyTrait>> Consumer<T> {
    pub fn new(inner: T) -> Consumer<T> {
        Consumer {
            inner: inner
        }
    }
    pub fn do_stuff(&self) {
        self.inner.borrow().function1();
    }
}

fn main() {
    // I want to eventually be able to swap this out for x = Rc::new(MyStruct ...
    // but I'll keep it simple for now.
    let x = MyStruct { number: 42 };
    let c = Consumer::new(x);
    c.do_stuff();
}

目前这还不能工作,因为MyStruct实现了Borrow<MyStruct>,但没有实现Borrow<MyTrait>。好的,让我们尝试实现它:

impl Borrow<MyTrait> for MyStruct {
    fn borrow(&self) -> &MyTrait {
        self
    }
}

这导致了以下错误,我不理解:
<anon>:33:5: 35:6 error: method `borrow` has an incompatible type for trait:
 expected bound lifetime parameter ,
    found concrete lifetime [E0053]
<anon>:33     fn borrow(&self) -> &MyTrait {
<anon>:34         self
<anon>:35     }
<anon>:33:5: 35:6 help: see the detailed explanation for E0053
error: aborting due to previous error
playpen: application terminated with error code 101
什么?这里没有提到任何具体的生命周期,而且“Borrow”也没有定义任何生命周期。我很困惑。
首先,我的假设是使用“Borrow”是正确的方法吗?如果是,那么如何实现一个trait的“Borrow”呢?
2个回答

7
正确的实现方式如下:
impl<'a> Borrow<MyTrait + 'a> for MyStruct {
    fn borrow(&self) -> &(MyTrait + 'a) {
        self
    }
}

特质对象可以通过生命周期限定进行限制。这是因为实现特质的类型可能包含引用,在某些情况下,我们需要能够区分依赖于借用对象和不依赖于借用对象的对象。如果没有指定生命周期限定,我认为它默认为'static;然而,指定&(MyTrait + 'static)作为返回类型也可以编译(它不够通用,因此应该优先考虑通用解决方案),所以你遇到的问题比那更微妙...


1
啊,谢谢!我以前从未见过这种 &(MyTrait + 'a) 语法。我之前在其他类型的泛型参数或类型边界中看到过它,但从未单独使用过。 - Bill Fraser

4
作为旁注,我找到了一种替代方法来做这件事,它不需要完全实现Borrow<MyTrait>
我们可以让Consumer接受一个额外的参数,指定实际借用类型将是什么,然后约束该类型实现该特性。像这样:
impl<T: Borrow<Borrowed>, Borrowed: MyTrait> Consumer<T, Borrowed> {

这需要Consumer具有一个引用否则未使用的Borrowed类型参数的PhantomData成员。以下是完整的实现代码:
struct Consumer<T, Borrowed> {
    inner: T,
    phantom: PhantomData<Borrowed>
}

impl<T: Borrow<Borrowed>, Borrowed: MyTrait> Consumer<T, Borrowed> {
    fn new(inner: T) -> Consumer<T, Borrowed> {
        Consumer {
            inner: inner,
            phantom: PhantomData
        }
    }
    pub fn do_stuff(&self) { // this function is the same as before.
        self.inner.borrow().function1();
    }
}

这种替代方案的好处在于,它允许在具有通用方法的特质中使用特质,因为它不需要创建任何特质对象(无法为具有通用函数的特质创建特质对象:参见https://doc.rust-lang.org/error-index.html#method-has-generic-type-parameters)。
唯一的缺点是现在必须向Consumer提供有关其通用参数的提示。您必须告诉它所涉及的具体类型:
fn main() {
    let x = MyStruct { number: 42 };
    let c = Consumer::<_, MyStruct>::new(x);
    c.do_stuff();
}

2
Borrowed 类型参数是必要的,因为一个类型可以实现多个 Borrow<T> 实例。如果 Borrow 有一个关联类型而不是类型参数,那么你的 impl 上的额外类型参数就不是必要的了;你可以使用 where 子句添加一个约束条件,比如 T::Target: MyTraitDeref 有一个关联类型,但它没有针对 Target = TT 实现。你可能可以定义自己的 trait,但在特化或负 trait 约束实现之前,你无法利用现有的 trait 提供实现。 - Francis Gagné

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接