Storing a partially initialised struct as a field in another struct

Hi,

I am trying to resolve a problem where I want to store a struct as a field within another struct, however at the point of initialisation, the inner struct wont have to all of the data needed for its own initialisation. Below is an example of what I want to do.

struct A {
    inner_struct: B
}

struct B {
    data: Vec<f64>
}

impl A {
    fn new() -> A {
        A {
            inner_struct: B {
                vec![0.0]
            }
        }
    }

    fn some_function(&mut self, data: Vec<f64>) {
        self.inner_struct.data = data
    }
}

When initialising A, I have had to put some "dummy" data for the B struct, which feels wrong. I have read about Builder struct, but Im not sure how to implement that for this exact scenario, because would I not then have to say that inner_struct must be either B or its builder, or if it is the best method.

Any help would be much appreciated.

Thanks
Karl

An empty vector doesn't allocate, so you can just use Vec::new().

In general, Default is expected to be cheap. If there isn't a Default for your type, wrap it in an Option.

Thanks for the reply!

Question with regards to the Option. The B struct is only missing the data for a relatively small part of the total code, as the data is provided early, just not at the beginning. And in my mind, having unwraps in the rest of the code feels wrong. Is this actually the case, or should I not worry about having unwrap whenever I call B?

This is what the builder pattern is for. Assuming there's more to these types that you've cut out, what I'd do is to have A, B, ABuilder, and BBuilder types.

struct A { inner: B }
struct B { data: Vec<f64> }

#[derive(Default)] struct ABuilder { inner: BBuilder }
#[derive(Default)] struct BBuilder { data: Option<Vec<f64>> }

impl ABuilder {
    fn build(self) -> A {
        A { inner: self.inner.build() }
    }
    fn set_data(&mut self, data: Vec<f64>) {
        self.inner.data = Some(data);
    }
}
impl BBuilder {
    fn build(self) -> B {
        B { data: self.data.expect("need to set BBuilder.data!") }
    }
}

Or something like that. That way, you divide the life of an A into two distinct periods: prior to having all data available (where things that might not yet be set use Option), and after having all data available (where all the Options disappear).

Aside: the other alternative if you have a simple two-phase initialisation is to just pass data as an argument to build.

To be complete: there is a way to do potentially uninitialised values in Rust, but do not use it. It involves unsafe, and it should be an absolute last resort when you have profiled your code and definitely require it for measurable performance reasons. Using it for this would be like using a flamethrower to kill a cockroach: it'll probably work, but you run the risk of burning the whole house down.

2 Likes