Search code examples
ruststructinitializationmember

How to use a struct without initializing its members?


I'm trying to use a struct without initializing some of its members, to be able to use it:

struct Structure {
    initialized: bool,
    data: StructureData, // This is another struct...
    target: TargetTrait, // This member that I don't need to initialize.
}

impl Structure {
    pub fn initialize(&mut self) -> bool {
        if self.initialized {
            false
        } else {
            // Here I should initialize the target...
            self.target = initializeTarget(...); // No problem...
            self.initialized = true;
            true
        }
    }

    pub fn new(&self) -> Structure {
        Structure {
            initialized: false,  // The initialize method will do that...
            data: StructureData, // Initializing Structure data behind the
            // scenes...
            // Here the struct needs some value for the target, but I cannot provide any value,
            // Because the initialization method will do that job...
            target: None, // I tried using (None), and Option<T>,.
                          // But the TargetTrait refused.
        }
    }
}

// main function:
fn main() {
    let structure: Structure = Structure::new(); // It should construct a new structure...
    if structure.initialize() {
        // Here, I should do some work with the target...
    }
}

I tried to use Option, but the target trait doesn't implement Option, or even

#[derive(Default)]

Solution

  • This could be solved using the Typestate pattern, under the form of a builder type.

    The basic idea behind this pattern is that invalid states shouldn't be representable at runtime (eg. by changing the value of the initialized field, in this case). Instead this should be handled by the type system at compile time.

    For example, here you'd use two distinct types. A first "uninitialised" builder type that represents the state of the structure without the target present, and the initialized Structure containing the target. It does not contain an initialized field though, since by virtue of being built by the other class, it can only represent an initialized state (hence the Type State).

    struct Structure {
        data: StructureData,
        target: TargetTrait,
    }
    
    struct StructureBuilder {
        data: StructureData,
    }
    
    impl StructureBuilder {
        pub fn initialize(self) -> Structure {
            // Here I should initialize the target...
            Structure {
                data: self.structureData,
                target: initializeTarget(),
            }
        }
    
        pub fn new() -> StructureBuilder {
            StructureBuilder {
                data: StructureData, // Initializing Structure data behind the scenes
            }
        }
    }
    

    This type takes care of initializing the target with the initialize method.

    However, the method signature (taking self by value) makes sure that the instance of StructureBuilder cannot be reused afterwards, and the newly created Structure takes ownership of the data being moved into it.

    It would then be used like this:

    fn main() {
        let builder = StructureBuilder::new();
        let structure = builder.initialize();
    }