Preserve referenced value alongside reference

Hi all, I am very new to Rust and quite excited about it, but I am stuck on a particular issue. Consider these two structs:

struct A {
    x: u32
}

struct B<'a> {
    a: &'a A
}

impl<'a> B<'a> {
    fn new(a: &'a A) -> Self {
        Self { a }
    }
}

I cannot change these structs since they are part of an external crate. Because A and B are implementation details, I want to wrap them in a single struct that implements certain traits and, importantly, ensures that the relevant instance of A lives long enough for B::a to remain a valid reference.

My naive attempt was this:

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

impl<'a> C<'a> {
    fn new() -> Self {
        let a = A { x: 0 };
        let b: B<'a> = B::new(&a);
        C { a, b }
    }
}

But this leads to the error: error[E0597]: a does not live long enough

In C++, I'd do this with a heap allocation such that, even when moving the pointer, the reference remains valid. So I tried using Box<A> instead of A within struct C, but the same error prevails. (And then Pin<Box<A>>.)

In other words, I want to avoid the need for the user of C to allocate a struct A. I want a single instance of C to properly manage both A and B referencing said A.

I understand the error message, but I am at a loss for how to work around this considering that I cannot change A or B. I'd very much appreciate any pointers!

Thank you very much!

The keyword to search for is "self-referential" (or "self-referencing") structs. Rust doesn't support this natively, but typically one can either try to avoid it, or make it work with crates like self_cell - Rust or yoke - Rust

1 Like

It's called self-referential structs. You should use a crate to do that.

self_cell::self_cell!(
    struct C {
        owner: A,

        #[covariant]
        dependent: B,
    }

    impl {Debug}
);

fn main() {
    let c = C::new(A { x: 123 }, |a| B::new(a));
    dbg!(&c, c.borrow_owner().x, c.borrow_dependent().a);
}

code

2 Likes

Thank you for the helpful responses @steffahn and @vague, I really appreciate it!

@steffahn, you wrote "typically one can either try to avoid it". If I cannot modify A and B, is there a straightforward way to work around this problem?

No, avoiding the problem would either mean re-structuring the module that defines A and B such that you don’t need the struct B with a lifetime (e.g. with an alternative “owned” API or something..), or it means that you do end up offering your wrapping API in a similarly split manner, either living with the “ need for the user of C to allocate a struct A” that you wanted to avoid, or if that’s too much “implementation detail” coming up with your own 2-part wrapping API, where one half (say C1) contains the A and another half (say C2<'a>) contains the B<'a>.

4 Likes

Another way to look at this is that it normally isn't a good idea for a library crate to have an API where a struct contains a reference, since this makes it very difficult to use.

There are cases where this is Ok, for example when B is intended to be a temporary or very short lived value. In that case, you normally wouldn't normally need to put A and B together in another struct.

If you can be more concrete -- tell us the crate and why you want to put A and B together -- then we may be able to say more about the specific situation.

4 Likes