heapless icon indicating copy to clipboard operation
heapless copied to clipboard

Mutual referencing and self-referencing structures not possible with Vec, FnvIndexMap, SortedLinkedList but with std variants

Open systec-ms opened this issue 3 years ago • 4 comments

Good day, sorry I don't really know how to name the problem, so here is a "minimal" example.

The following code will lead me into this error:

error[E0597]: `holder.b` does not live long enough
  --> src/main.rs:39:39
   |
39 |     holder.c.lookup.borrow_mut().push(&holder.b);
   |                                       ^^^^^^^^^ borrowed value does not live long enough
40 | }
   | -
   | |
   | `holder.b` dropped here while still borrowed
   | borrow might be used here, when `holder` is dropped and runs the destructor for type `Holder<'_>`
use core::cell::RefCell;

use heapless::{FnvIndexMap, Vec};

struct A {}

struct B<'a> {
    ref_a: RefCell<Option<&'a A>>,
}

struct C<'a> {
    lookup: RefCell<Vec<&'a B<'a>, 4>>,
}

struct Holder<'a> {
    c: C<'a>,
    b: B<'a>,
}

fn main() {
    let c = C {
        lookup: RefCell::new(Vec::new()),
    };

    let b = B {
        ref_a: RefCell::new(None),
    };

    let holder = Holder { c, b };

    holder.c.lookup.borrow_mut().push(&holder.b);
}

But with std::vec::Vec it is fine: lookup: RefCell<Vec<&'a B<'a>>>, And also with a simple array.

use core::cell::RefCell;

use heapless::{FnvIndexMap, Vec};

struct A {}

struct B<'a> {
    ref_a: RefCell<Option<&'a A>>,
}

struct C<'a> {
    lookup: RefCell<[Option<&'a B<'a>>; 4]>,
}

struct Holder<'a> {
    c: C<'a>,
    b: B<'a>,
}

fn main() {
    let c = C {
        lookup: RefCell::new([None;4]),
    };

    let b = B {
        ref_a: RefCell::new(None),
    };

    let holder = Holder { c, b };

    holder.c.lookup.borrow_mut()[0] = Some(&holder.b);
}

I would expect this to be fine with heapless::Vec as well. The same problem exists with the heapless::FnvIndexMap which uses heapless::Vec internally. Can this have something to do with the const initialization here ? So my question is this intentional/ a trade-off that was deliberately chosen?

systec-ms avatar May 31 '22 13:05 systec-ms

The error is also reproducible with an even further simplified variant:

use core::cell::RefCell;
use heapless::Vec;

struct B {}

struct C<'a> {
    lookup: RefCell<Vec<&'a B, 4>>,
}
struct Holder<'a> {
    c: C<'a>,
    b: B,
}
fn main() {
    let b = B {};
    let c = C {
        lookup: RefCell::new(Vec::new()),
    };
    let holder = Holder { c, b };
    holder.c.lookup.borrow_mut().push(&holder.b);
}
error[E0597]: `holder.b` does not live long enough
  --> examples/error2.rs:19:39
   |
19 |     holder.c.lookup.borrow_mut().push(&holder.b);
   |                                       ^^^^^^^^^ borrowed value does not live long enough
20 | }
   | -
   | |
   | `holder.b` dropped here while still borrowed
   | borrow might be used here, when `holder` is dropped and runs the destructor for type `Holder<'_>`

Again with std Vec (Playground) or an Array (Playground) this is fine.

systec-ms avatar Jun 04 '22 13:06 systec-ms

thanks for the bug report. I wasn't immediately sure why the behavior is different in this case. I originally thought it was a borrowck error related to (lifetime) variance so I checked that heapless::Vec is covariant like alloc::Vec and it is covariant so that side is clear.

upon further digging this seems to be dropck error. heapless::Vec behaves differently because it doesn't have a #[may_dangle] attribute in its Drop implementation like alloc::Vec does. I have confirmed that adding the #[may_dangle] attribute to heapless::Vec makes your snippet compile.

unfortunately, the #[may_dangle] attribute is unstable and doesn't seem like it has a clear stabilization path from the error message that you get when you try to use it without the dropck_eyepatch feature gate. this means that this issue is currently "nofix" until the features gets stabilized, if it does at all.

P.S. I want to add that your code looks a bit like it's trying to create a self-referential struct; in that case you may want to try the Pin abstractions to see if you can achieve what you want in a different manner.

japaric avatar Jun 15 '22 15:06 japaric

(labeling this a 'bug' because we want parity with libstd collections but also with 'blocked' because this hinges on may_dangle or a equivalent mechanism being stabilized)

japaric avatar Jun 15 '22 15:06 japaric

Thanks for taking a look and digging into it.

Also thanks for the hint with Pin, I am aware of it. By using safe Rust, however, I see no possibility that the Rust compiler allows a move. And thereby no use for Pin.

systec-ms avatar Jun 15 '22 21:06 systec-ms