What do you call this pattern? conditionally transforms `Self` (usually builder types)

I find myself often write this pattern, that is, conditionally call a method on a builder:

let builder = Builder::new()
	.with_foo(foo)
	.with_bar(bar);
let builder = if let Some(baz) = maybe_baz {
	builder.with_baz(baz)
} else {
	builder
};
let object = builder.build();

or alternatively with mut and assignment:

let mut builder = Builder::new()
	.with_foo(foo)
	.with_bar(bar);
if let Some(baz) = maybe_baz {
	builder = builder.with_baz(baz)
}
let object = builder.build();

it's not a big deal really, but it's just too often for me to ignore. so I came up with this little device:

trait MapOption<U>: Sized {
	fn map_option(self, o: Option<U>, f: impl FnOnce(Self, U) -> Self) -> Self;
}
impl<T, U> MapOption<U> for T {
	fn map_option(self, o: Option<U>, f: impl FnOnce(Self, U) -> Self) -> Self {
		match o {
			Some(u) => f(self, u),
			None => self,
		}
	}
}

now I can write the same code in this style:

let object = Builder::new()
	.with_foo(foo)
	.with_bar(bar)
	.map_option(baz, Builder::with_baz)
	.build();

I deem this pattern to be fairly common in builder pattern, but I didn't hear people talk about it. so I wonder what name is this pattern usually called? and what's alternatives to map_option()? any idea? thanks.

2 Likes

Lots of extension-trait chaining helpers can be found in the tap library. However, it doesn't seem to have anything like this where the concept is that an auxiliary value (not the self value) is optional, so it doesn't suggest any naming.

3 Likes

I have used something sort of similar with some applications that use tracing. The Layer trait is implemented for Option<L> where L is a Layer so you can write .with(optional_layer). It's pretty ergonomic especially combined with Option::map (e.g. .with(config.optional_layer.map(init))).

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.