What is a good way to resolve this borrow issue?

Basically I have three structs:

  • the first encapsulates the other two ("CStruct")
  • the second creates/owns an instance of an object ("AStruct")
  • the third requires a reference to the object of the second struct to create objects for itself ("BStruct")

The actual code is somewhat extensive, so I've taken only the essential parts and simplified the names to reproduce the issue:

enum Instance {
    Something,
    SomethingElse
}

struct AStruct {
    inst: Instance
}

struct BStruct<'a> {
    r_inst: &'a Instance
}

struct CStruct<'a> {
    astruct: AStruct,
    bstruct: BStruct<'a>
}

fn create_astruct() -> AStruct {
    AStruct{inst: Instance::SomethingElse}
}

fn create_bstruct<'a>(astruct: &'a AStruct) -> BStruct<'a> {
    BStruct{r_inst: &astruct.inst}
}

fn create_cstruct<'a>() -> CStruct<'a> {
    let astruct = create_astruct();
    let bstruct = create_bstruct(&astruct);
    CStruct{astruct, bstruct}
}

fn main() {
    let _cstruct = create_cstruct();
}

(Playground)

Errors:

   Compiling playground v0.0.1 (/playground)
error[E0515]: cannot return value referencing local variable `astruct`
  --> src/main.rs:30:5
   |
29 |     let bstruct = create_bstruct(&astruct);
   |                                  -------- `astruct` is borrowed here
30 |     CStruct{astruct, bstruct}
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^ returns a value referencing data owned by the current function

error[E0505]: cannot move out of `astruct` because it is borrowed
  --> src/main.rs:30:13
   |
27 | fn create_cstruct<'a>() -> CStruct<'a> {
   |                   -- lifetime `'a` defined here
28 |     let astruct = create_astruct();
29 |     let bstruct = create_bstruct(&astruct);
   |                                  -------- borrow of `astruct` occurs here
30 |     CStruct{astruct, bstruct}
   |     --------^^^^^^^----------
   |     |       |
   |     |       move out of `astruct` occurs here
   |     returning this value requires that `astruct` is borrowed for `'a`

error: aborting due to 2 previous errors

Some errors have detailed explanations: E0505, E0515.
For more information about an error, try `rustc --explain E0505`.
error: could not compile `playground`

To learn more, run the command again with --verbose.

I would have expected this to work since both structs end up being moved in the first and thus should have the same lifetime, unfortunately it seems the borrow checker doesn't agree with me.

So any hints on how to resolve this would be appreciated, preferably without having to change the relations/structure of the structs.

In rust, a struct with a lifetime parameter (containing references with that lifetime) always borrows data that lives outside of that struct. For

struct BStruct<'a> {
    r_inst: &'a Instance
}

this seems to match your intuition as-well, but crucially the same logic applies to

struct CStruct<'a> {
    astruct: AStruct,
    bstruct: BStruct<'a>
}

as-well. The reference inside of the BStruct<'a> inside of the CStruct<'a> still only can borrow things that are not part of the CStruct<'a> itself, hence you can’t have it borrow from the AStruct that is part of the CStruct<'a>.

The term for what you’re trying to achieve here is a “self-referential” data type / struct. Rust doesn’t support those, unfortunately. If it really is needed, there are however crates out there that can help you create those kinds of data types anyways. For example ouroboros is such a crate. Even with such a crate, you’d need to make one important change: Introduce some indirection for the AStruct. Since Rust allows data do be moved when moving CStruct, the contained AStruct would change its location in memory, so if the BStruct did contain a reference to it, that reference would be outdated / invalidated.


However, self-referential data structures like this are usually avoided if they aren’t necessary. Often, we split things up between multiple data types, one part would contain your AStruct, and another data-type could be created with a reference to that first part and ultimately contain your BStruct. Or something like that… well – look, you’ve successfully simplified your example so much that it’s impossible to know what it actually is that you’re achieving with your code in the first place :wink:. Feel free to tell us more about the context and maybe we can find better alternative solutions here!

There’s examples of this kind of approach e.g. in the standard library. You can have a Vec<T> and then you can create an iterator over it (of type slice::Iter<'a, T>) from a &'a Vec<T> that borrows the Vec. The popular crate itertools offers some interesting iterator adapters like GroupBy that produces a bunch of Groups. Those do need to operate on some shared underlying data-structure in order to avoid unnecessary allocations when you access them sequentially. In order to achieve this, the GroupBy struct itself isn’t actually an iterator, but you (as the crate user) need to store it in some variable etc. yourself, then borrow it and turn the &GroupBy reference into an iterator. A &'a GroupBy reference creates a Groups<'a> iterator which returns Group<'a> items. (I’ve left out the other generic type parameters for simplicity)

2 Likes

Thanks for the explanation, I already read about Rust's issues with self-referential data structures but wasn't sure if my implementation was such a case (I assume it is in the context of CStruct).

To be honest I don't necessarily need to structure my code this way, the main reason for doing so was to logically separate certain data members and functions so I wouldn't end up with one mega struct in a single .rs file that contains all the code (so much so that there's actually more than one BStruct).

More specifically I'm trying to implement a Vulkan renderer using the ash crate, where the Instance member of the sample code stands for a ash::Device struct.
The reference in BStruct (and others like it) is then used to create/update/drop its own data members throughout the code using the various methods of the reference.

It would be nice if a possible solution would at least still allow for such a logical separation in different files since I already have structured most of the code this way, but alternative suggestions are of course also welcome :slight_smile:

ash::Device only seems to have &self methods. So if you want to have multiple references to the same ash::Device and avoid lifetime problems, it might be straightforward to work with Rc<ash::Device>.

Code example in the AStruct/BStruct context:

use std::rc::Rc;

enum Instance {
    Something,
    SomethingElse,
}

struct AStruct {
    inst: Rc<Instance>,
}

struct BStruct {
    r_inst: Rc<Instance>,
}

struct CStruct {
    astruct: AStruct,
    bstruct: BStruct,
}

fn create_astruct() -> AStruct {
    AStruct {
        inst: Rc::new(Instance::SomethingElse),
    }
}

fn create_bstruct(astruct: &AStruct) -> BStruct {
    BStruct {
        r_inst: Rc::clone(&astruct.inst),
    }
}

fn create_cstruct() -> CStruct {
    let astruct = create_astruct();
    let bstruct = create_bstruct(&astruct);
    CStruct { astruct, bstruct }
}

fn main() {
    let _cstruct = create_cstruct();
}

(playground)

1 Like

Thanks, that indeed seems to solve the issue (still in the learning stage of when certain Rust types are appropriate/necessary, but every bit helps).

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.