A question about ownership


#1

Hello!

I have a question regarding the rust ownership system. Suppose I have an A struct,

struct A {
       ...
}

and a B struct, which references an A:

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

I can create vector of As, xs, and a vector of Bs, ys, like this:

let mut xs = Vec::new();
xs.push(A::new(...));

let mut ys = Vec::new();
ys.push(B::new(&xs[0],...));

vector bs is larger and many Bs use a reference to the same A.

So far so good, but I would like to create a struct C, which holds both
vectors, like this:

struct C {
   xs: Vec<A>,
   ys: Vec<B<?>> // this Bs use As from ys
}

The problem is, I don’t know what lifetime to use for B, because it’s not
'static, and C should not have any lifetime parameters, because it owns all data.

The pointer structure in this setup looks reasonable: no cycles or mutable refs,
but I can’t write code that creates it.

At the moment, I work around the problem by having B hold an Rc<A>, but I
really don’t like the solution:

  • It fails to represent the implied ownership model, that C owns all the
    data.

  • As are just “somewhere in the heap”, instead of being stored nicely
    side by side in a particular vector.

  • I cannot make my C type sync because of Rc (I can use Arc, but again,
    it looks like a solution to the wrong problem)

So, how can I create such a sturct C, that owns a vector of As and a vector of
A-referencing Bs?


#2

You can use indexes instead of references.


#3

Rust currently doesn’t have a way to store references to data on the heap which is owned by a struct in that same struct. I think this is mainly due to not having any way of differentiating data on the stack and on the heap, and it’s pretty much unreasonable to reference data on the stack that’s stored inside the struct referencing it (because then that struct can never move).

The most viable solution to this currently, as @gkoz stated, is to have B store an index, and to have some way of “reading” the B by using a C.

struct B {
    a: usize
}

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

impl C {
    fn get_b<'a>(&'a self, index: usize) -> BView<'a> {
        let b = &self.ys[index];
        let a = &self.xs[b.a];

        BView { b: b, a: a }
    }
}

That being said, it is possible to do this kind of using unsafe code, by transmuting the reference to &'static. That is, define B as struct B { &'static A }, and when creating B that is only ever owned by C, use std::mem::transmute to turn the reference from xs into a static reference to store in ys. I remember a thread somewhere about someone doing this safely with String/&'static str, but I can’t seem to find it. I would highly recommend using indexes, but this is another viable solution if done carefully.


#4

I suspect it is me.

Edit: I did not manage to quote the topic directly; here is a link.

    let t = Test { id : "foo".to_owned(), val : 42 };
    let mut h : HashMap <&str, Test> = HashMap::new();
    // h.insert(t.id.clone(), t); // works
    let id = t.id.as_str() as *const str;
    unsafe {
        h.insert(&*id, t);
    }

It did work, but I decided to try doing it the Rust way first to see how it goes.


#5

Thanks! At first I thought that storing and index would be awkward, but it turned out quite ergonomic at the end!


#6

I think this is mainly due to not having any way of differentiating data on the stack and on the heap

Nice, now I understand why my initial setup was inexpressible in safe Rust.