Search code examples
rustreferencelifetimelifetime-scoping

Rust apparent non-associativity of lifetimes


In this minimal working example, we have traits Vector and Matrix and a struct MyMatrix modeled as a Vector of (Row)Vectors. The traits have a method "get" that returns a reference to an element. For the matrix we first get a reference to a RowVector and then from it a reference to a matrix element.

trait Vector {
    type T;
    fn get(&self,i:usize) -> &Self::T;
}

trait Matrix {
    type F;
    fn get(&self,i:usize,j:usize) -> &Self::F;
}

struct MyMatrix<C>(C);

impl<C:Vector<T=R>,R:Vector<T=F>,F> Matrix for MyMatrix<C> {
    type F=F;

    fn get(&self,i:usize,j:usize) -> &Self::F {
        // ERROR:
        // the parameter type `R` may not live long enough
        // ...so that the type `R` will meet its required lifetime bound
        self.0
            .get(i)
            .get(j)
    }
}

Edit: As Suggested here is the full error message using cargo check

error[E0311]: the parameter type `R` may not live long enough
  --> playground/src/main.rs:20:9
   |
16 |       fn get(&self,i:usize,j:usize) -> &Self::F {
   |              ----- the parameter type `R` must be valid for the anonymous lifetime defined here...
...
20 | /         self.0
21 | |             .get(i)
   | |___________________^ ...so that the type `R` will meet its required lifetime bounds
   |
help: consider adding an explicit lifetime bound
   |
16 |     fn get<'a>(&'a self,i:usize,j:usize) -> &'a Self::F where R: 'a {
   |           ++++  ++                           ++         +++++++++++

As you can see we get an error regarding lifetimes. I don't understand the error as to my knowledge the lifetime of the output reference of Vector::get should live as long as the input reference of Vector::get, so R should live, as long as self/matrix.

I could add lifetime annotations to methods/structs/traits to fix this error. However this would force me to add lifetimes annotation across my whole library which would get quite messy. I would much prefer a solution without lifetime annotations. Probably we could also fix the issue by changing the definitions of traits and struct MyMatrix. For example using T and F as a generic type, and not as an associated type. However this would also imply significant changes to my library. I would be interested in a solution which keeps the basic structure and implements my intention that the output of the get functions have the same lifetimes as the input.


Solution

  • This happen because you didn't specify the lifetime of the type, since it is a trait, it can be implement for any type, non-static reference is also a type

    consider this:

    trait Vector {
        type T;
        fn get(&self,i:usize) -> &Self::T;
    }
    
    trait Matrix {
        type F;
        fn get(&self,i:usize,j:usize) -> &Self::F;
    }
    
    struct MyVector<C>(Vec<C>);
    struct MyMatrix<V>(V);
    
    impl<C> Vector for MyVector<C> {
        type T = C;
        fn get(&self,i:usize) -> &Self::T {
            &self.0[i]
        }
    }
    
    impl<C:Vector<T=R>,R:Vector<T=F>,F> Matrix for MyMatrix<C> {
        type F=F;
    
        fn get(&self,i:usize,j:usize) -> &Self::F {
            self.0
                .get(i)
                .get(j)
        }
    }
    
    fn main () {
        let data: Vec<i32> = vec![1,2,3];
    
        let data_ref: Vec<&i32> = data.iter().collect();
    
        let my_vector: MyVector<&i32> = MyVector(data_ref);
        
        let my_matrix: MyMatrix<Vec<MyVector<&i32>>> = MyMatrix(vec![my_vector]);
    
        let my_entry : &i32 = my_matrix.get(0, 1);
    
        drop(my_matrix); // <--- 
        // the source of the reference drop before the reference
        // this might happen if C is a non static reference instead of a 'static type 
    }
    

    To solve that you must guarantee your get borrow must live long enough,

    • one of the ways is to do as the compiler suggested in the error message, that way C can still be &'a SomeConcreteType; or
    • make sure that the type that implements vector will have a static lifetime, aka a concrete type or a 'static reference

    I would suggest the second method since I don't think you would want to store reference instead of owned value in the entry of a matrix.

    trait Vector : 'static {
        type T;
        fn get(&self,i:usize) -> &Self::T;
    }
    

    related: