Search code examples
rusttraitslifetimeboxing

How does boxing a trait affect lifetime of parameters passed to it ? (with a very specific example)


Here is a very simple but specific example that produces a compilation error I cannot understand:

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

When the line marked (*) is commented, everything is fine. However, if uncommented, the compiler complains about entry not living long enough, see the line marked "here".

I fail to understand how the wrapper happens to change the way the path is borrowed.

EDIT

As pointed out by @Jmb below, this has nothing to do with Path, and the same problem arises with a simple &str, for example:

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

Solution

  • Although the previous answer and comments provide very useful insights about lifetime inferences in this particular case, they don't come with a practical solution.

    I finally found out the following one. First let's simplify a bit the problem, using a String for now:

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

    Of course it fails exactly for the same reason as before:

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

    However, if one changes MyTrait so T is passed by reference in the signature of my_func, and adapts the rest accordingly:

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

    Then the foobar() function can be left unchanged, but now it compiles.

    And, as stated by @kmdreko below, it will also work for str or or other non-sized types like Path, with the following modifications:

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

    Then, to come back to the initial use case, the following code now works as expected:

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

    Bottomline

    See @Jmb's answer and associated comments for some explanations about why the first solution doesn't compile.