#Quite odd rust compiler borrow checking error

1 messages · Page 1 of 1 (latest)

elfin kraken
#

While programming in rust, I recently stumbled upon a problem I can niether explain nor fix myself. Here is a rather long minimal example, for which I'm sure the problem still occurs:

extern crate hashbrown;
use std::{cell::{OnceCell, RefCell}, collections::HashMap};
use hashbrown::HashMap as HHashMap;

struct Caches<'ctx> {
    pool: HHashMap<&'ctx (), ()>,
}

impl<'ctx> Default for Caches<'ctx> {
    fn default() -> Self {
        Self { pool: Default::default() }
    }
}

struct Context<'ctx> {
    caches: Caches<'ctx>
}

impl<'ctx> Context<'ctx> {
    fn new() -> Context<'ctx> {
        Self { caches: Caches::default() }
    }
}

struct App<'ctx> {
    ctxt_cell: OnceCell<Context<'ctx>>,
    ctxt: RefCell<Option<&'ctx Context<'ctx>>>
}

impl<'ctx> App<'ctx> {
    fn context(&'ctx self) -> &'ctx Context<'ctx> {
        self.ctxt.borrow_mut().get_or_insert_with(|| self.ctxt_cell.get_or_init(|| Context::new()))
    }
}

pub fn build_app<T, F>(f: F) -> T
where
    F: for<'ctx> FnOnce(&'ctx App<'ctx>) -> T
{
    let app = App {
        ctxt_cell: OnceCell::new(),
        ctxt: RefCell::new(None)
    };

    f(&app)
}

fn main() {
    build_app(|app| {
        let _ctxt = app.context();
    });
}

It's a bit infuriating code, but since this is part of a larger project, I've refactored it already, while the error still occurs there though, I can't manage to recreate a minimal example, for which the error still happens.

Trying to compile it, rustc gives an oddly inspecific error: error[E0597]: `app` does not live long enough (Run it at play.rust-lang.org, to see the full error).
To my eyes, this seems weird, as I expect the closure should be fully done with app once it returns. Even more so, and this is why I really can't help myself with the error, it fully vanishes once you replace HHashMap (the one from hashbrown) with HashMap (the one from std). Which is even more confusingly just wrapping hashbrown. Can somebody expalain this behaviour?

primal forge
#

-errors

lunar doveBOT
#

Run cargo check in a terminal

Note: If using rust analyzer you can click the "click for full compiler diagnostic" link in your editor.

Please post the full output of the above command, including the error title and any help or notes. An example of how this looks is:

error[E0308]: mismatched types
 --> src/main.rs:3:17
  |
3 | let foo: &i32 = bar;
  |          ----   ^^^ expected `&i32`, found integer
  |          |
  |          expected due to this
  |
help: consider borrowing here
  |
3 | let foo: &i32 = &bar;
  |                 +

When posting the error put it in a code block so it has nice formatting:

```rust
// error from cargo check here
```

Please do not post a screenshot. If the output is too long then use a paste tool like https://paste.rs/web

elfin kraken
#

Heres the compiler error message, as it didn't fit within the original post ```
error[E0597]: app does not live long enough
--> src/main.rs:45:7
|
40 | let app = App {
| --- binding app declared here
...
45 | f(&app)
| ^^^^ borrowed value does not live long enough
46 | }
| -
| |
| app dropped here while still borrowed
| borrow might be used here, when app is dropped and runs the destructor for type `App<'_>

primal forge
#
fn context(&'ctx self) -> &'ctx Context<'ctx>

cant work where you are returning the insides of a guard

elfin kraken
#

It did work just before trying to switch to hashbrown for HashMaps, so that can't really be the issue. Also, as mentioned above, I've already refactored this part of the code, but while the error still occurs within the project I can't manage to break it down into a minimal example.

primal forge
#

the reason for the difference between the hashmap types is that the std can use rustc_insignificant_dtor to promise that the Drop wont touch the &'ctx ()

shrewd lagoon
#

this looks like an attempt to create a self-referential type with ctxt containing a reference to a value owned by the ctxt_cell field of the same struct

#

that's fundamentally not going to work

primal forge
elfin kraken
#

Thank you for your help so far. Now I did get rid of the self referential part, and also broke it down some more. The problem seems to be stemming from having structs with dtors within any sort of cells. Here's an example:

use std::{cell::{UnsafeCell}, marker::PhantomData};

struct WithDtor<T>(PhantomData<T>);

impl<T> Default for WithDtor<T> {
    fn default() -> Self {
        Self(PhantomData)
    }
}

impl<T> Drop for WithDtor<T> {
    fn drop(&mut self) { }
}

struct Context<'ctx> {
    with_cell: UnsafeCell<WithDtor<&'ctx ()>>
}

impl<'ctx> Context<'ctx> {
    fn new() -> Context<'ctx> {
        Self { with_cell: UnsafeCell::new(Default::default()) }
    }
}

struct App;

impl App {
    fn context<F: for<'ctx> FnOnce(&'ctx Context<'ctx>) -> R, R>(&self, f: F) -> R {
        let ctxt = Context::new();
        f(&ctxt)
    }
}

fn main() {
    let app = App;
    app.context(|_ctxt| { });
}

With the following error message:

error[E0597]: `ctxt` does not live long enough
  --> src/main.rs:30:11
   |
29 |         let ctxt = Context::new();
   |             ---- binding `ctxt` declared here
30 |         f(&ctxt)
   |           ^^^^^ borrowed value does not live long enough
31 |     }
   |     -
   |     |
   |     `ctxt` dropped here while still borrowed
   |     borrow might be used here, when `ctxt` is dropped and runs the destructor for type `Context<'_>`

Removing either the Cell or the Drop implementation for WithDtor makes the example compile. How can I get around this?

solemn crest
elfin kraken
#

Yes, I did. And elliding that lifetime, does allow this example to compile. But in my project it is essential, that the Context and its refrence share a lifetime. Maybe this constraint is a bit too much, but it did work just before trying to combine "significant" dtors with Cells.

solemn crest
#

them sharing a lifetime is the problem here