Struct that owns some data and a reference to the data

16.3k views Asked by At

Construction of an object allocates data needed for lifetime of that object, but also creates another object that needs to keep references to the data:

pub fn new() -> Obj {
    let data = compute();

    Obj {
        original: data,
        processed: AnotherObj {
            reference: &data
        }
    }
}

Is it possible to express this in Rust's terms?

Here I'd like Obj, AnotherObj and data to have the same lifetime, and of course outlive the new() call.

1

There are 1 answers

1
mdup On BEST ANSWER

A raw design of the structs based on your requirements might look like this:

struct AnotherObj<'a> {
    original: &'a Vec<i8>, // Let's agree on Vec<i8> as your "data" type.
}

struct Obj<'a> {
    original: Vec<i8>,         // <-------------------+
    processed: AnotherObj<'a>, // should point here --+
}

However it's very tricky to get working (personally, I wasn't able to) because you want the 'a in AnotherObj<'a> to be the lifetime of original. However you must supply a lifetime to Obj<'a> and thus you would have to specify Obj<'tbc> where 'tbc is the lifetime of the Obj to be created.

I suggest the following alternatives:

1. Make AnotherObj actually own the original

Why not? Obj will own AnotherObj, so it can still have access to original as a nested child:

pub struct AnotherObj {
    original: Vec<i8>,
}

pub struct Obj {
    processed: AnotherObj,
}

pub fn new() -> Obj {
    let data = vec![1,2,3];

    Obj {
        processed: AnotherObj {
            original: data,
            // ...
        }
    }
}

// access as obj.processed.original, you can even create a getter `fn original(&self)`

2. Shared pointer design

Straightforward use of refcounted pointers:

use std::rc::Rc;

pub struct AnotherObj {
    original: Rc<Vec<i8>>,
}

pub struct Obj {
    original: Rc<Vec<i8>>,
    processed: AnotherObj,
}

pub fn new() -> Obj {
    let data = Rc::new(vec![1,2,3]);

    Obj {
        original: data.clone(),
        processed: AnotherObj {
            original: data.clone(),
        }
    }
}

3. With raw pointers

Options 1. and 2. will bring you the peace of mind of the safe Rust gods, therefore I don't recommend this third option. I still post it here for completeness. Note: it compiles, but I never tested it at runtime, so it may bite. There's only safe code below but you'll have to go in unsafe land when you want to dereference the raw pointer.

use std::ptr;

pub struct AnotherObj {
    original: *mut Vec<i8>,
}

pub struct Obj {
    original: Vec<i8>,
    processed: AnotherObj,
}

pub fn new() -> Obj {
    let data = vec![1,2,3];

    let mut obj = Obj {
        original: data,
        processed: AnotherObj {
            original: ptr::null_mut(),
        }
    };
    obj.processed.original = &mut obj.original as *mut Vec<i8>;

    obj
}