4

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?

Oyren
  • 143
  • 5

1 Answers1

5

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.

Netwave
  • 40,134
  • 6
  • 50
  • 93
  • 1
    Thanks, the inner scope was just to make the lifetime more clear. A usage would look more like [this](https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=23c5cc60f9776da5dc9fa75355f41db6) (I will probably run in all kind of issue) but as mentioned I would assume that this would not compile. – Oyren May 30 '22 at 12:27
  • 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. – Oyren May 30 '22 at 12:38
  • Why do you think this should not compile?: Because I had a more complex program where the compiler was yelling at me that a reference didn't live long enough and pointing me to the end of the main function, with "dropped here while still borrowed. borrow might be used here, when `name` is dropped and runs the destructor for type `Name<'_>". I thought there was a connection here, but I think I caught my mistake. I had built a self-referential struct via a closure capturing. Thanks. – Oyren May 30 '22 at 21:01