Lifetimes in Rust - one difficult exercise
Tags: rust, programming
Published: 2024-05-26 19:02:38

The excellent online book *Rust By Practice* in the *Lifetime* chapter has a difficult exercise called... *A difficult exercise*. I struggled with it for a while, then looked at the solution and - I still did not get it. Not until I read the chapter on ownership from *The Rustonomicon*. Let's dive into the problem and describe the solution in detail.

First we need to go over the code in the exercise and explain what it does. It defines a structure called `List`; that structure has nothing to do with an actual list, it is just a container for a single object. The object it holds is an instance of a `Manager` structure. The `List` gives mutable access to the `Manager` but not directly - instead it creates container called `Interface` that holds mutable reference to the `Manager`. This is done in `get_interface` method that returns `Interface`. The only method of `Interface` is `noop(self)`.


```rust
struct Interface<'a> {
    manager: &'a mut Manager<'a>
}

impl<'a> Interface<'a> {
    pub fn noop(self) {
        println!("interface consumed");
    }
}

struct Manager<'a> {
    text: &'a str
}

struct List<'a> {
    manager: Manager<'a>,
}

impl<'a> List<'a> {
    pub fn get_interface(&'a mut self) -> Interface {
        Interface {
            manager: &mut self.manager
        }
    }
}

fn main() {
    let mut list = List {
        manager: Manager {
            text: "hello"
        }
    };

    list.get_interface().noop(); // <--- Interface created and used here.

    println!("Interface should be dropped here and the borrow released");

    use_list(&list);
}

fn use_list(list: &List) {
    println!("{}", list.manager.text);
}
```

When trying to run this code, rust fails to compile it with the following error:

![Compilation error](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pee6he8ycwafugztdubi.png)

According to rust's lifetime rules, borrow is valid from it's declaration to it's last use. Last use of `Interface` is when `noop()` is called in the `main` function. It should be similar to this example:

```rust
fn main() {
    let mut x = 1;
    let y = &mut x;
    // print!("{x}"); <-- this will not compile, 
    //                    x is mutably borrowed so print cannot borrow it again
    
    *y = 42; // last use of y
    
    print!("{x}"); // x no longer borrowed, can be used
}
```

So why it does not work? Let's analyze reference lifetimes in the `main` function. I am using similar notation that the *The Rustonomicon* is using, showing scopes of each lifetime. Note this is not valid rust syntax!

```rust
fn main() {
    'a {
        let mut list = List { // list has lifetime 'a
            manager: Manager {
                text: "hello"
            }
        };        

        'b {
            list.get_interface().noop(); // Interface uses lifetime 'b
        }

        // 'b is out of scope
        println!("Interface should be dropped here and the borrow released");

        use_list(&list);
    } // lifetime 'a ends here
}
```

But... this still does not explain the issue. `'b` lifetime is short and ends before `use_list` is called. To understand what happens we need to look closer at `get_interface()` method declaration:

```rust
impl<'a> List<'a> {
    pub fn get_interface(&'a mut self) -> Interface {
        Interface {
            manager: &mut self.manager // what is lifetime of this?
        }
    }
}
```

Rust lifetime elision allows us to skip some lifetime parameters. If we make all lifetimes explicit, the declaration will look as below

```rust
impl<'a> List<'a> {
    pub fn get_interface(&'a mut self) -> Interface<'a> {
        Interface::<'a> {
            manager: &'a mut self.manager
        }
    }
}
```
The lifetime `'a` from above snippet will be the same as the lifetime `'a` from `main` function since we are calling this method on `list` object in `main`. This means `Interface.manager` will hold reference with `'a` lifetime. And if you look inside `Manager` struct it uses this lifetime in `text` reference. Recall the implementation of `Interface`:

```rust
struct Interface<'a> {
    manager: &'a mut Manager<'a>
}

```

Even if we stop using `Interface`, the `manager` reference is still alive as it is using `'a` lifetime, which in `get_interface` will be bound to `List`'s lifetime also called `'a` here. 

How do we fix this? We need to untangle `Interface`'s lifetime from `Manager`'s lifetime. Let's introduce separate lifetime `'b` for Interface's reference. I will consistently use `'a` and `'b` which should make it easier to track which lifetime is which.

```rust
struct Interface<'b, 'a> {
    manager: &'b mut Manager<'a> // reference with lifetime 'b
                                 // holding Manager with lifetime 'a
}

impl<'b, 'a> Interface<'b, 'a> {
    pub fn noop(self) {
        println!("interface consumed");
    }
}

```

We could bind the lifetimes declaring `'a: 'b` to indicate `'a` outlives `'b`, but this is not necessary in this example.

The last thing remaining is to create `Interface` instance with proper lifetimes. `List` uses only one lifetime `'a`, but we can extend `get_interface` method to take another generic lifetime parameter `'b`. 

```rust
impl<'a> List<'a> {
    pub fn get_interface<'b>(&'b mut self) -> Interface<'b, 'a> {
        Interface {
            manager: &mut self.manager
        }
    }
}

```

Now we explicitly told rust to use `'b` lifetime for reference inside `Interface`. Once usage is done, rust can release the reference and `list` becomes available.

*The Rustonomicon* describes itself as *the awful details that you need to understand when writing Unsafe Rust programs*. In this example we did not do any unsafe rust, yet the knowledge from that book was very helpful. I recommend reading *The Rustonomicon* even if you only plan to play safe.

Sources:
1. https://doc.rust-lang.org/nomicon/lifetimes.html
2. https://practice.course.rs/lifetime/advance.html
3. https://doc.rust-lang.org/nomicon/lifetime-elision.html