Trying to understand Rc / Chaining

Hi.
I’m trying to understand Rc but am having trouble finding information of wether what I am trying is even possible. Chaining functions is a common Pattern in Rust, however I am unsure of how it interacts with Rc. Specifically, I am currently working on a problem where I have two Structs, and one is an extension of the other with only one information added. Instances of TypeA will not change during runtime once they were created and their number is limited. TypeB is derived from an instance of TypeA, and there will be many instances of TypeB created and put in a further datastructure, so I understand using an Rc is desirable to avoid .clone(), for both runtime and memory reasons.

I was trying to implement this using the builder pattern, but the naive operation obviously doesn’t work, because self will be moved:

#[derive(Default)]
struct TypeA {
   very_important_data: Vec<u32>,
   some_other_data: u32,
}

struct TypeB {
   data: Rc<TypeA>,
   position: u64,
}
impl TypeA {
   fn into_type_b(self, pos: u64) -> TypeB {
      TypeB {
         position: pos,
         data: Rc::from(self),
      }
   }
}
_____
let a = TypeA::default();
let b = a.into_type_b(0);
//This next line cannot compile because a was moved above
let b2 = a.into_type_b(1);
_______
///This compiles, but doesn’t follow the pattern I was hoping was possible
fn into_type_b(a: Rc<TypeA>, pos: u64) {
   TypeB {
      position: pos,
      data: a,
   }
}

I tried a few other things such as using &self or trying to implement for impl Rc<TypeA> but neither of that seemed to work either.

Is it possible to follow that pattern with Rc at all?

Of course one might ask the question of why not use a simple reference: data: &'a TypeA, but that quickly escalates with an exponentionally growing number of lifetime annotations throughout the code and that is frankly beyond my Rust level of competence for now.

Thanks for reading and any help

There are several pieces to doing this well, but the most important thing to understand is that creating Rc<TypeA> (which you are doing with Rc::from(self)) doesn’t help you share a value with its previous owner. You have to have already put the TypeA into Rc when you start sharing it. Here is a working version:

use std::rc::Rc;
#[derive(Default)]
struct TypeA {
    very_important_data: Vec<u32>,
    some_other_data: u32,
}
struct TypeB {
    data: Rc<TypeA>,
    position: u64,
}

impl TypeA {
    fn into_type_b(self: Rc<Self>, pos: u64) -> TypeB {
        TypeB {
            position: pos,
            data: self,
        }
    }
}

fn main() {
    let a = Rc::<TypeA>::default();
    let b = a.clone().into_type_b(0);
    let b2 = a.into_type_b(1);
}

If all uses of TypeA will involve Rc, with no exceptions whatsoever, then you can make this more tidy by putting the Rc inside TypeA:

#[derive(Clone, Default)]
struct TypeA(Rc<TypeAInner>);

#[derive(Default)]
struct TypeAInner {
    very_important_data: Vec<u32>,
    some_other_data: u32,
}

impl TypeA {
    fn into_type_b(self, pos: u64) -> TypeB {
        ...
4 Likes

The short reply to this portion is that reference are generally for temporary borrows,[1] not your typical long-lived data owning data structures, and are thus not a solution for the use case.


  1. and lifetimes denote the duration of a borrows, not the liveness scope of values ↩︎

2 Likes

Thank you very much, I think is the solution to my problem. I implemented this in my actual project and it seems to work fine.

1 Like