Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does this mutual referencing structures work in Rust with specified lifetimes?

Why does the following code compiles?

I would expect the Rust compiler to tell me

"borrowed value does not live long enough" when setting the reference (store.barber.set(Some(&barber));

Since barber has a shorter lifetime than shop.

use core::cell::Cell;

struct Shop<'a> {
    barber: Cell<Option<&'a Barber<'a>>>,
}

struct Barber<'a> {
    shop: Cell<Option<&'a Shop<'a>>>,
}

fn main() {
    let shop = Shop { barber: Cell::new(None) };
    {
        let barber = Barber { shop: Cell::new(Some(&shop))};
        shop.barber.set(Some(&barber));
    }
}

Playground

My assumption is confirmed by @jmb with his answer (here).

The code example comes from here. But why the whole thing works or where my misunderstanding lies is still unclear to me.

Edit/Enlightenment

The code which I commented to on @Netwaves answer.

In case the link don't work anymore and also to clarify the question.

The inner scope was just to make the lifetime more clear.

A usage would look more like this:

use core::cell::Cell;

struct Shop<'a> {
    barber: Cell<Option<&'a Barber<'a>>>,
    shop_state: Cell<bool>,
}

impl<'a> Shop<'a> {
    fn change_barber_state(&self) {
        self.barber.get().unwrap().change_state();
    }

    fn change_state(&self) {
        self.shop_state.set(!self.shop_state.get());
    }
}

struct Barber<'a> {
    shop: Cell<Option<&'a Shop<'a>>>,
    barber_state: Cell<bool>,
}

impl<'a> Barber<'a> {
    fn change_state(&self) {
        self.barber_state.set(!self.barber_state.get());
    }

    fn change_shop_state(&self) {
        self.shop.get().unwrap().change_state();
    }
}

fn main() {
    let shop = Shop {
        barber: Cell::new(None),
        shop_state: Cell::new(false),
    };

    let barber = Barber {
        shop: Cell::new(Some(&shop)),
        barber_state: Cell::new(false),
    };
    shop.barber.set(Some(&barber));

    println!("{:?}", barber.barber_state);
    println!("{:?}", shop.shop_state);

    shop.change_barber_state();
    barber.change_shop_state();

    println!("{:?}", barber.barber_state);
    println!("{:?}", shop.shop_state);
}

Is the lifetime the same through the same scope?

I thought the lifetime is also given by freeing the resources, which happens in reverse order to the declaration. Or is this only relevant if the drop trait is implemented?

like image 990
Oyren Avatar asked Sep 07 '25 08:09

Oyren


1 Answers

Why does this mutual referencing structures work in Rust with specified lifetimes?

Short answer:

It does because as per your code it actually lives long enough.

Explanation

The problem (no problem) is that you are not using shop anymore after the inner scope, so the compiler is smart enough to say nothing is wrong with your program. But if you add an access he will start complaining, and with a good reason:

fn main() {
    let shop = Shop { barber: Cell::new(None) };
    {
        let barber = Barber { shop: Cell::new(Some(&shop))};
        shop.barber.set(Some(&barber));
    }
    shop.barber.get();
}

fails to compile with:

error[E0597]: `barber` does not live long enough
  --> src/main.rs:15:30
   |
15 |         shop.barber.set(Some(&barber));
   |                              ^^^^^^^ borrowed value does not live long enough
16 |     }
   |     - `barber` dropped here while still borrowed
17 |     shop.barber.get();
   |     ----------------- borrow later used here

Playground

On the question extension:

fn main() {
    let shop = Shop {
        barber: Cell::new(None),
        shop_state: Cell::new(false),
    };

    let barber = Barber {
        shop: Cell::new(Some(&shop)),
        barber_state: Cell::new(false),
    };
    shop.barber.set(Some(&barber));

    println!("{:?}", barber.barber_state);
    println!("{:?}", shop.shop_state);

    shop.change_barber_state();
    barber.change_shop_state();

    println!("{:?}", barber.barber_state);
    println!("{:?}", shop.shop_state);
}

Why do you think this should not compile?

Clearly both shop and barber life for the same extent, up to when main is finished. It doesn't matter the order of free in this case, since the compiler already know that none of them will be used anymore, so the above code is completely safe.

like image 193
Netwave Avatar answered Sep 10 '25 08:09

Netwave