1

Why does this code:

#[derive(Eq, PartialEq, Ord, PartialOrd)]
enum List<'a> {
    Cons(isize, &'a List<'a>),
    Nil,
}

fn main() {
    use List::*;
    use pinus::{prelude::*, sync::PineMap};
    let table = PineMap::new();
    table.insert(Nil, Nil);
    let nil = table.get(&Nil).unwrap();
    table.insert(Cons(1, nil), Cons(1, nil));
}

cause this error:

error[E0597]: `table` does not live long enough
  --> src/main.rs:12:15
   |
12 |     let nil = table.get(&Nil).unwrap();
   |               ^^^^^^^^^^^^^^^ borrowed value does not live long enough
13 |     table.insert(Cons(1, nil), Cons(1, nil));
14 | }
   | -
   | |
   | `table` dropped here while still borrowed
   | borrow might be used here, when `table` is dropped and runs the `Drop` code for type `PineMap`

I'm using a pinus::sync::PineMap to try to make sure that all references to equivalent instances of List are actually references of the same object in memory. I thought a way to do this would be to have a table mapping List keys to a table-owned instance of List, and I'm trying out PineMap for it because its insert will not move its items in memory (its insert borrows &self not &mut self too) so references to its List values will stay valid, and I can build self-referential items.

Why is table still considered to be borrowed at the end of its scope in my code?

kmdreko
  • 42,554
  • 6
  • 57
  • 106
theonlygusti
  • 11,032
  • 11
  • 64
  • 119

1 Answers1

1

When you get nil out of the table and put it back into it with Cons(1, nil), the type of nil is a reference that is tied to the lifetime of the table, and thus the lifetime 'a of the Lists that you're storing are bound to the table that is storing them.

The core problem here is that this creates a self-referential struct, which introduces a myriad of problems. You wouldn't be able to call any of the mutable methods either because the table is permanently borrowing itself immutably.

The specific problem that the compiler is complaining about is that, since the elements hold a reference to the table, when its being dropped the drop logic may try to access data that is in the process of being destroyed, which is not sound.

I encourage you to read about the Drop Check in the Rustonomicon for more info, here's a guiding principle though:

For a generic type to soundly implement drop, its generics arguments must strictly outlive it.

I don't have any workarounds or solutions for you other than you can't do it this way.

kmdreko
  • 42,554
  • 6
  • 57
  • 106
  • Please can you help me understand why making a new scope just for nil doesn't work either? I asked a new question about it: https://stackoverflow.com/q/74644342/3310334 – theonlygusti Dec 01 '22 at 15:29