rust 封装trait如何影响传递给它的参数的生命周期?(举一个非常具体的例子)

dwthyt8l  于 2023-10-20  发布在  其他
关注(0)|答案(2)|浏览(80)

下面是一个非常简单但具体的例子,它产生了一个我无法理解的编译错误:

use std::path::Path;

trait MyTrait<T> {
    fn my_func(&self, t: T);
}

struct MyImpl {}

impl MyTrait<&Path> for MyImpl {
    fn my_func(&self, t: &Path) {
        println!("{:?}", t)
    }
}

struct MyWrapper<T> {
    inner: Box<dyn MyTrait<T>>
}

impl<T> MyWrapper<T> {
    pub fn new(inner: Box::<dyn MyTrait<T>> ) -> Self { 
        Self { inner } 
    }
}

impl<T> MyTrait<T> for MyWrapper<T> {
    fn my_func(&self, t: T) {
        self.inner.my_func(t);
    }
}

fn foobar() {
    let the_impl = MyImpl{};        
    //let the_impl = MyWrapper::new(Box::new(the_impl)); // (*) 
    
    for entry in walkdir::WalkDir::new("blah") {
        let entry = entry.unwrap(); 
        let path = entry.path(); // <== here
        the_impl.my_func(path);
    }
}

当标记(*)的行被注解时,一切都很好。但是,如果未注解,编译器会抱怨entry的生存时间不够长,请参阅标记为“here”的行。
我无法理解 Package 器是如何碰巧改变路径被借用的方式的。

编辑

正如@Jmb在下面指出的,这与Path无关,简单的&str也会出现同样的问题,例如:

impl MyTrait<&str> for MyImpl {
    fn my_func(&self, t: &str) {
        println!("{:?}", t)
    }
}

fn foobar_str() {
    let the_impl = MyImpl{};        
    let the_impl = MyWrapper::new(Box::new(the_impl));
    {
        let s = String::from("blah").clone();
        the_impl.my_func(&s as &str); // <== same error
    }
}
kb5ga3dv

kb5ga3dv1#

path在某个生存期'a中具有类型&'a Path,该生存期仅对单个循环迭代有效(直到entry被丢弃)。
当被 Package 在 Package 器中时,the_impl具有MyWrapper<T>类型,用于某种推断类型T
因为你调用了the_impl.my_func (path),编译器推断T == &'a Path,所以the_impl的类型是MyWrapper<&'a Path>,它不能存在超过'a的生存期。因此,由于the_impl的错误需要存在于整个循环中。
当你没有 Package the_impl时,它有类型MyImpl,它为所有的生存期'b * 实现MyTrait<&'b Path> *,包括比the_impl的生存期短的生存期(它们只需要足够长,就可以调用my_func)。因此编译器可以使用MyTrait<&'a Path>实现,而不会影响the_impl的生存期。
这里的Path类型没有什么特别之处,但您的&str实现可能有。我怀疑在后一种情况下,您最终会得到&'static str,如果需要,它可以永远存在。

k2arahey

k2arahey2#

虽然前面的答案和评论在这个特定的情况下提供了关于寿命推断的非常有用的见解,但是它们并没有带来实际的解决方案。
我终于找到了下面的一个。首先让我们简化一下问题,现在使用String

trait MyTrait<T> { fn my_func(&self, t: T); }

struct MyImpl {}

impl MyTrait<&String> for MyImpl {
    fn my_func(&self, t: &String) { println!("{}", t) }
}

struct MyWrapper<T> { inner: Box<dyn MyTrait<T>> }

impl<T> MyTrait<T> for MyWrapper<T> {
    fn my_func(&self, t: T) { self.inner.my_func(t); }
}

当然,它失败的原因和以前完全一样:

fn foobar() {
    let the_impl = MyImpl{};
    let the_impl = MyWrapper { inner: Box::new(the_impl) };
    {
        let s = String::from("blah");
        the_impl.my_func(&s); // <== error: 's' does not live long enough
    }
}

然而,如果改变MyTrait,使Tmy_func的签名中通过引用传递,并相应地调整其余部分:

trait MyTrait<T> { fn my_func(&self, t: &T); } // <== main change here

struct MyImpl {}

impl MyTrait<String> for MyImpl {
    fn my_func(&self, t: &String) { println!("{}", t) } // <== note the actual signature hasn't changed
}

struct MyWrapper<T> { inner: Box<dyn MyTrait<T>> }

impl<T> MyTrait<T> for MyWrapper<T> {
    fn my_func(&self, t: &T) { self.inner.my_func(t); }
}

然后foobar()函数可以保持不变,但现在它编译
而且,正如@kmdreko在下面所说的,它也适用于str或其他非大小类型,如Path,但需要进行以下修改:

trait MyTrait<T: ?Sized> { fn my_func(&self, t: &T); }

struct MyWrapper<T: ?Sized> { inner: Box<dyn MyTrait<T>> }

impl<T: ?Sized> MyTrait<T> for MyWrapper<T> {
    fn my_func(&self, t: &T) { self.inner.my_func(t); }
}

然后,回到最初的用例,下面的代码现在可以按预期工作:

impl MyTrait<Path> for MyImpl {
      fn my_func(&self, t: &Path) { println!("{:?}", t) }
}

fn foobar_with_path_in_a_loop() {
    let the_impl = MyImpl{};        
    let the_impl = MyWrapper { inner: Box::new(the_impl) };
    
    for entry in walkdir::WalkDir::new("blah") {
        let entry = entry.unwrap();
        let path = entry.path();
        the_impl.my_func(path);
    }
}

底线

请参阅@Jmb的回答和相关评论,了解有关第一个解决方案无法编译的原因的一些解释。

相关问题