Search code examples
pointersrustlifetimetrait-objects

The lifetime of trait object pointer


I encountered a compile error related to lifetime in my Rust code. Here is the code causing the error:

fn to_pointer_vec(ptrs: &[*const dyn ToString]) -> Vec<*const dyn ToString> {
    let mut vec: Vec<*const dyn ToString> = Vec::new();
    for &ptr in ptrs {
        vec.push(ptr);
    }
    vec
}

The error message can be seen below:

 --> src/table/merger.rs:78:9
   |
73 |     fn to_pointer_vec(ptrs: &[*const dyn ToString]) -> Vec<*const dyn ToString> {
   |                             - let's call the lifetime of this reference `'1`
...
78 |         vec
   |         ^^^ returning this value requires that `'1` must outlive `'static`
   |
help: to declare that the trait object captures data from argument `ptrs`, you can add an explicit `'_` lifetime bound
   |
73 |     fn to_pointer_vec(ptrs: &[*const dyn ToString]) -> Vec<*const dyn ToString + '_> {
   |                                                                                ++++

I don't understand why the trait object pointer has a lifetime constraint.


Solution

  • Raw pointers don't have lifetimes, but trait objects do.

    A trait object refers to any type satisfying some trait bound (in your example T: ToString), and this type could potentially have a lifetime parameter. The compiler needs to assume some minimum lifetime for a trait object, and RFC 599 spells out the rules how this lifetime is inferred if it's not explicitly provided. Spelling out the elided lifetimes in your function prototype results in this:

    fn to_pointer_vec<'a>(ptrs: &'a [*const dyn ToString + 'a]) -> Vec<*const dyn ToString + 'static>
    

    In other words, the lifetime of the trait objects that are passed in is inferred as 'a, while the lifetime of the returned trait objects is inferred as 'static. This explains the error message, which is admittedly confusing in this case. The types do look identical, but they are not.

    There are several ways to fix this. First, you could do as the compiler suggests and tell the compiler to infer the lifetime based on the input by adding a '_ lifetime to the return value:

    fn to_pointer_vec(ptrs: &[*const dyn ToString]) -> Vec<*const (dyn ToString + '_)>
    

    You could also use a generic type parameter T:

    fn to_pointer_vec<T: Clone>(ptrs: &[T]) -> Vec<T>
    

    This guarantees that the T in the parameter and the T that is returned are the same. This works for your example code, but might not work for you actual code, since you lose the information that T is a raw pointer to some trait object.

    And finally, for you example code you don't need the function at all. It's equivalent to ptrs.to_vec().