Builder Pattern with Option Types

I'm working with a library that relies on the builder pattern (rust_xlsxwriter).

When using the library to build something like a chart, you'd use code like this to setup the axes:

    chart
        .x_axis()
        .set_name("X Axis Name")
        .set_min(0)
        .set_max(10000)
        .set_log_base(10)
        .set_major_gridlines(true)

The problem is that the values I have for most of these items are optional:

pub struct AxisData {
    pub name: Option<String>,
    pub min: Option<i32>,
    pub max: Option<i32>,
    pub log_base: Option<u32>,
    pub major_gridlines: Option<bool>,
    pub minor_gridlines: Option<bool>
}

I understand I could solve this by using if let statements and setting each field individually:

if let Some(name) = axis_data.name {
  axis.set_name(name)
}
// repeat for each field
...

But I was wondering if there was a cleaner way to do this, like some way to only call the function in the builder pattern if an option contained a Some value?

You could use Option::inspect for each field and call the ChartAxis method in a closure. But this is probably not much better.

A more radical idea is to change whatever code is creating AxisData to create a ChartAxis instead. The problem is the redundancy, so perhaps you can get rid of AxisData.

I think the best solution is probably moving the sequence of if-lets to an extension trait so that all of your call-sites just look something like this:

chart.x_axis().update(x_axis_data);
chart.y_axis().update(y_axis_data);
1 Like

if let is probably the best choice in my opinion. the thing is, the alternatives are not really that much of an improvement after all. but there is things you can do if you really need the (very small) benefits.

for example, you can check the trick I presented in this post.

and if you really have A LOT of these fields, you can always use a macro to reduce the repetition, but not by much (you need to both pass the field identifier (like name) and the setter identifier (like set_name) anyway),

e.g. this:

if let Some(name) = axis_data.name {
  axis.set_name(name)
}
if let Some(min) = axis_data.min {
  axis.set_min(min)
}
if let Some(max) = axis_data.max {
  axis.set_max(max)
}
// ...

might become this:

macro_rules! set {
    ($setter:ident, $field:ident) => {
        if let Some($field) = axis_data.$field {
            axis.$setter($field)
        }
    }
}
set!(set_name, name);
set!(set_min, min);
set!(set_max, max);
//...
3 Likes