Structure lifetimes for structs that reference a value contained in the parent

I have the following structs:

struct MyValue {
   name: String,
   element: &Element
}

struct Container {
    name: String,
    list: Vec<Element>,
    current: Option<MyValue>
}

struct Element {
    name: String,
    cost: i16
}

The relationship is like :

                       contains
     Container ----------------------------> List of Element
         |                                         |
         | aggregates                              | Reference on 1 list item
         `-----------------> Value <---------------' 

I would like to be able to have a parent container which could contains a reference on one element of a list which is also contained by the parent container.

Is it possible to have lifetimes in the structs i have troubles when trying to add lifetimes and use methods for exploiting the values.

Or is this approach flawed ? What would be the right design to have a structure like that ?:

  • a container
  • a reference on one element of the container

Thanks

This is called a self-referential struct and you can't do it. Standard alternatives involve indexes into vectors and replacing references with Rc.

This is an instance of trying to create a self-refecencing struct. There are 4 workarounds, each having different advantages and disadvantages:

  • Don’t bundle the Option<MyValue> with the rest of the Container at all:

    Often it’s not necessary to combine these two things. You can just split Container into two parts if you will, e.g.

    struct ContainerOwnedPart {
        name: String,
        list: Vec<Element>,
    }
    
    struct Element {
        name: String,
        cost: i16,
    }
    
    struct ContainerBorrowedPart<'a> {
        current: Option<MyValue<'a>>,
    }
    
    struct MyValue<'a> {
        name: String,
        element: &'a Element,
    }
    
  • Use indices:

    You could use an index instead of the &Element reference. The methods working with this index then also need to borrow the Vec of the containing Container but that’s often not a problem (e.g. just make them methods of Container itself, or require an additional &Container or &[Element] argument)

    struct Container {
        name: String,
        list: Vec<Element>,
        current: Option<MyValue>
    }
    
    struct Element {
        name: String,
        cost: i16
    } 
    
    struct MyValue {
       name: String,
       element: usize,
    }
    
  • Use Rc (or Arc if you need multi-threaded access):

    If you really want to bundle the things together in Container and don’t like using indices either, you could use shared ownership of the Elements by wrapping them into Rc

    use std::rc::Rc;
    
    struct Container {
        name: String,
        list: Vec<Rc<Element>>,
        current: Option<MyValue>,
    }
    
    struct Element {
        name: String,
        cost: i16,
    }
    
    struct MyValue {
        name: String,
        element: Rc<Element>,
    }
    
  • Use crates that support actual self-referencing structs without using unsafe code yourself:

    Under similar preconditions as the previous approach, there’s also the option of using crates such as ouroboros

    These can be a bit harder to work with, but they don’t have the (small) performance overhead of Rc, however in this particular example, a self-referencing struct would no longer allow you to modify the Vec once the struct is created. If all you want to be able to do, modification-wise, is add further elements, you could switch to something like typed_arena, which offers a &self method for adding new elements.

      use ouroboros::self_referencing;
    use typed_arena::Arena;
    
    #[self_referencing]
    struct Container {
        name: String,
        list: Arena<Element>,
    
        #[covariant]
        #[borrows(list)]
        current: Option<MyValue<'this>>,
    }
    
    struct Element {
        name: String,
        cost: i16,
    }
    
    struct MyValue<'a> {
        name: String,
        element: &'a Element,
    }
    

Ok, thank you.

That's weird, I can't understand why it's self-referencing, cause the reference contained in MyValye comes from Vec<Element> which is not in the same hierarchy (agregations are parallels).

However, i accept this strange thing.

“Parallel” references in structs are also considered part of the category “self-referencing” structs. Note that crates like ouroboros, pretty-much only support this kind of “parallel” self-referencing type of structs, i.e. one field of a struct referencing another; or similarly a part of one field referencing a part of another field.

You might be thinking only of cases of self-referencing structs where a reference is able to cycle back to the containing struct itself, i.e. reference-cycles, or perhaps you’d also consider the case where MyValue would be referencing the whole Vec<Element> field, self-referencing, I don’t know where your intuition draws the boundary, but do note that in the context of Rust, conventionally your struct would indeed be considered “relf-referencing”, too, and the current ownership system of Rust does not directly support them.

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.