Why are explicit lifetimes needed in Rust?

前端 未结 10 1195
别那么骄傲
别那么骄傲 2020-11-22 16:08

I was reading the lifetimes chapter of the Rust book, and I came across this example for a named/explicit lifetime:

struct Foo<\'a> {
    x: &\'a i         


        
10条回答
  •  不要未来只要你来
    2020-11-22 16:43

    As a newcomer to Rust, my understanding is that explicit lifetimes serve two purposes.

    1. Putting an explicit lifetime annotation on a function restricts the type of code that may appear inside that function. Explicit lifetimes allow the compiler to ensure that your program is doing what you intended.

    2. If you (the compiler) want(s) to check if a piece of code is valid, you (the compiler) will not have to iteratively look inside every function called. It suffices to have a look at the annotations of functions that are directly called by that piece of code. This makes your program much easier to reason about for you (the compiler), and makes compile times managable.

    On point 1., Consider the following program written in Python:

    import pandas as pd
    import numpy as np
    
    def second_row(ar):
        return ar[0]
    
    def work(second):
        df = pd.DataFrame(data=second)
        df.loc[0, 0] = 1
    
    def main():
        # .. load data ..
        ar = np.array([[0, 0], [0, 0]])
    
        # .. do some work on second row ..
        second = second_row(ar)
        work(second)
    
        # .. much later ..
        print(repr(ar))
    
    if __name__=="__main__":
        main()
    

    which will print

    array([[1, 0],
           [0, 0]])
    

    This type of behaviour always surprises me. What is happening is that df is sharing memory with ar, so when some of the content of df changes in work, that change infects ar as well. However, in some cases this may be exactly what you want, for memory efficiency reasons (no copy). The real problem in this code is that the function second_row is returning the first row instead of the second; good luck debugging that.

    Consider instead a similar program written in Rust:

    #[derive(Debug)]
    struct Array<'a, 'b>(&'a mut [i32], &'b mut [i32]);
    
    impl<'a, 'b> Array<'a, 'b> {
        fn second_row(&mut self) -> &mut &'b mut [i32] {
            &mut self.0
        }
    }
    
    fn work(second: &mut [i32]) {
        second[0] = 1;
    }
    
    fn main() {
        // .. load data ..
        let ar1 = &mut [0, 0][..];
        let ar2 = &mut [0, 0][..];
        let mut ar = Array(ar1, ar2);
    
        // .. do some work on second row ..
        {
            let second = ar.second_row();
            work(second);
        }
    
        // .. much later ..
        println!("{:?}", ar);
    }
    

    Compiling this, you get

    error[E0308]: mismatched types
     --> src/main.rs:6:13
      |
    6 |             &mut self.0
      |             ^^^^^^^^^^^ lifetime mismatch
      |
      = note: expected type `&mut &'b mut [i32]`
                 found type `&mut &'a mut [i32]`
    note: the lifetime 'b as defined on the impl at 4:5...
     --> src/main.rs:4:5
      |
    4 |     impl<'a, 'b> Array<'a, 'b> {
      |     ^^^^^^^^^^^^^^^^^^^^^^^^^^
    note: ...does not necessarily outlive the lifetime 'a as defined on the impl at 4:5
     --> src/main.rs:4:5
      |
    4 |     impl<'a, 'b> Array<'a, 'b> {
      |     ^^^^^^^^^^^^^^^^^^^^^^^^^^
    

    In fact you get two errors, there is also one with the roles of 'a and 'b interchanged. Looking at the annotation of second_row, we find that the output should be &mut &'b mut [i32], i.e., the output is supposed to be a reference to a reference with lifetime 'b (the lifetime of the second row of Array). However, because we are returning the first row (which has lifetime 'a), the compiler complains about lifetime mismatch. At the right place. At the right time. Debugging is a breeze.

提交回复
热议问题