The impl Trait
syntax for return types seems to cause the compiler to incorrectly assume that the lifetime of the input argument must match the output in some situations. Consider the function
fn take_by_trait<T: InTrait>(_: T) -> impl OutTrait {}
If the input type contains a lifetime the compiler complains when the output outlives the it even though they are completely independent. This will not happen if the input type is not generic, or if the output is a Box<dyn OutTrait>
.
Full code:
trait InTrait {}
struct InStruct<'a> {
_x: &'a bool, // Just a field with some reference
}
impl<'a> InTrait for InStruct<'a> {}
trait OutTrait {}
impl OutTrait for () {}
fn take_by_type(_: InStruct) -> impl OutTrait {}
fn take_by_trait<T: InTrait>(_: T) -> impl OutTrait {}
fn take_by_trait_output_dyn<T: InTrait>(_: T) -> Box<dyn OutTrait> {
Box::new(())
}
fn main() {
let _ = {
let x = true;
take_by_trait(InStruct{ _x: &x }) // DOES NOT WORK
// take_by_type(InStruct { _x: &x }) // WORKS
// take_by_trait_output_dyn(InStruct { _x: &x }) // WORKS
};
}
Is there some elided lifetime here that I could qualify to make this work, or do I need to do heap allocation?
The impl Trait
semantic means that the function returns some type that implements the Trait
, but the caller cannot make any assumptions about which type that would be or what lifetimes it would use.
For all the compiler knows the take_by_trait
function can be used in many different modules, or, probably, in other crates. Now, your implementation would work fine in all use cases. It can be rewritten like
fn take_by_trait<T: InTrait>(_: T) -> () {}
This is a perfectly fine function and will work just fine. But then at some point you might want to add another implementation for the OutTrait
and change your take_by_trait
function a little.
trait OutTrait { fn use_me(&self) {} }
impl<T: InTrait> OutTrait for T {}
fn take_by_trait<T: InTrait>(v: T) -> impl OutTrait {v}
If we expand the generic parameter and impl
definition, we get this code:
fn take_by_trait<'a>(v: InStruct<'a>) -> InStruct<'a> {v}
fn main() {
let v = {
let x = true;
take_by_trait(InStruct{ _x: &x })
};
v.use_me();
}
This obviously cannot work because x
is dropped before println!
tries to access its value. So by adding a new implementation for the OutTrait
you broke code that uses this function, potentially somewhere in a crate that depends on yours. That's why the compiler is reluctant to allow you defining such things.
So, again, the issue with impl OutTrait
is just that the compiler cannot make any assumptions about the returned type and its lifetime, so it uses the maximum possible bound, which produces the borrow checker error you see.
EDIT: I've modified the code a little so that the signature of the function would not change and the code actually compiles and produces the same lifetime error: playground
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With