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