Search code examples
rustbuilderunsaferecursive-datastructures

Zero cost builder pattern for recursive data structure using transmute. Is this safe? Is there a better approach?


I would like to create a struct using the builder pattern which must be validated before construction, and I would like to minimize the construction overhead.

I've come up with a nice way to do that using std::mem::transmute, but I'm far from confident that this approach is really safe, or that it's the best approach.

Here's my code: (Rust Playground)

#[derive(Debug)]
pub struct ValidStruct {
    items: Vec<ValidStruct>
}

#[derive(Debug)]
pub struct Builder {
    pub items: Vec<Builder>
}

#[derive(Debug)]
pub struct InvalidStructError {}

impl Builder {
    pub fn new() -> Self {
        Self { items: vec![] }
    }
    
    pub fn is_valid(&self) -> bool {
        self.items.len() % 2 == 1
    }
    
    pub fn build(self) -> Result<ValidStruct, InvalidStructError> {
        if !self.is_valid() {
            return Err(InvalidStructError {});
        }
        unsafe {
            Ok(std::mem::transmute::<Builder, ValidStruct>(self))
        }
    }
}

fn main() {
    let mut builder = Builder::new();
    builder.items.push(Builder::new());
    let my_struct = builder.build().unwrap();
    
    println!("{:?}", my_struct)
}

So, this seems to work. I think it should be safe because I know the two structs are identical. Am I missing anything? Could this actually cause problems somehow, or is there a cleaner/better approach available?


Solution

  • You can't normally transmute between different structures just because they seem to have the same fields in the same order, because the compiler might change that. You can avoid the risk by forcing the memory layout but you're then fighting the compiler and preventing optimizations. This approach isn't usually recommended and is, in my opinion, not needed here.

    What you want is to have

    • a recursive data structure with public fields so that you can easily build it
    • an identical structure, built from the first one but with no public access and only built after validation of the first one

    And you want to avoid useless copies for performance reasons.

    What I suggest is to have a wrapper class. This makes sense because wrapping a struct in another one is totally costless in Rust.

    You could thus have

    /// This is the "Builder" struct
    pub struct Data {
        pub items: Vec<Data>,
    }
    pub struct ValidStruct {
        data: Data, // no public access here
    }
    impl Data {
        pub fn build(self) -> Result<ValidStruct, InvalidStructError> {
            if !self.is_valid() {
                return Err(InvalidStructError {});
            }
            Ok(Self{ data })
        }
    }
    

    (alternatively, you could declare a struct Builder as a wrapper of Data too but with a public access to its field)