colin-kiegel / rust-derive-builder

derive builder implementation for rust structs
https://colin-kiegel.github.io/rust-derive-builder/
Apache License 2.0
1.28k stars 82 forks source link

Feature Request: Infallible builder for struct-wide default. #281

Closed dscottboggs closed 1 year ago

dscottboggs commented 1 year ago

When #[builder(default)] is applied to the whole struct, and no validation function is specified, the build method is infallible. Is it possible for the build method to return the type itself rather than a Result in this case?

TedDriggs commented 1 year ago

This is something we've chosen not to address; it gets complicated when taking into account field-level defaults, custom constructors, validators, etc.

That said, there is a way to achieve this without

// Make the generated build function private, 
// change its name so we can use `build` for the public method,
// and tell it to use the crate's `UninitializedFieldError` so it doesn't emit a new type `ExampleBuilderError`
#[derive(Default, Builder)]
#[builder(default, build_fn(private, name = "try_build", error = "::derive_builder::UninitializedFieldError"))]
struct Example {
    foo: String,
}

// Note that we're adding an impl block to the _builder_, not the deriving struct
impl ExampleBuilder {
    pub fn build(&self) -> Example {
         self.try_build().unwrap()
    }
}

This presents out the desired API, while still using the generated body of the build function.

One drawback of this approach is that you'll no longer get a compile error if you were to add a non-defaulted field to the struct, or added a validator. You can address the former of these issues by adding a unit test like:

#[test] 
fn default_is_valid() {
    ExampleBuilder::default().build();
}

An alternative here might be to try using a type in #[build_fn(error = "...")] that doesn't have a conversion from derive_builder::UninitializedFieldError - that way any change to the struct that leads to using error-producing code in the body of the generated try_build method would lead to a compile error. I don't know if that's always going to be the case though, so you may find yourself needing to abandon this trick in the future.

I'm closing this out since we won't implement the requested feature, but please feel free to ask follow-up questions about the alternate solution here.