[Solved] Should my builder deref to what it builds?

Hi all

This is somewhat of an API design question. In my library I have a Builder that constructs a Unit:

pub struct Unit(usize);
pub struct Builder<'a>(&'a mut Unit);

impl Unit {
    fn some_data(&self) -> usize { self.0 }
}

impl<'a> Builder<'a> {
    fn unit(&self) -> &Unit { self.0 }
    fn set_some_data(&mut self, v: usize) { self.0.0 = v; } 
}

The Builder is used wherever a Unit needs to be mutated, in order to maintain internal invariants. Code using the builder will often want to access some data of the unit though:

fn main() {
    let mut unit = Unit(0);
    let mut builder = Builder(&mut unit);
    build_stuff(&mut builder);
}

fn build_stuff(builder: &mut Builder) {
    builder.set_some_data(42);
    let x = builder.unit().some_data();
    let y = builder.unit().some_data();
    builder.set_some_data(x*y);
    let z = builder.unit().some_data();
    builder.set_some_data(z*2);
}

This builder.unit() fragment appears a lot in client code. Should I for the sake of ergonomics have the Builder implement Deref and DerefMut to the underlying Unit?

Cheers,
Fabian

Let’s see the docs.

https://doc.rust-lang.org/std/ops/trait.Deref.html

Implementing Deref for smart pointers makes accessing the data behind them convenient, which is why they implement Deref . On the other hand, the rules regarding Deref and DerefMut were designed specifically to accommodate smart pointers. Because of this, Deref should only be implemented for smart pointers to avoid confusion.

So you shouldn’t. Instead, maybe you can add some methods like fn map(&mut self, impl FnOnce(Unit) -> Unit) to your builder?

3 Likes

That settles it then. I should have checked the docs more carefully… Thanks for the pointer and clarification! The idea about map is pretty nice, I’ll check whether I can swap this in easily.