Skip to content

Idea: Let the builder wrap a Result<T,Error> #25

@nielsle

Description

@nielsle

Here is a loose idea. It is mostly meant as an inspiration..

It could be to have a builder that wraps a Result. This would allow the builder to validate fields during the build process, and keep track of errors. Rust already allows you to do this with the try!(), macro, and the ?-operator has been accepted as RFC 243, but I still think that it could be nice to have a builder that keeps track of errors.

Here is what the code could look like

// The following only works on unstable
![feature(try_from)]
use std::convert::{TryFrom, TryInto};

#[derive(Debug)]
enum  MyError {
    TooOld
}

// I am not fully sure if it makes sense to annotate the fields
// #[builder(PersonBuilder, MyError)]
#[derive(Default)]
struct Person {
    name: String,
    // #[try_from]
    age: Age,
    // #[try_from]
    mail: Mail
}

#[derive(Default)]
struct Age(u32);

impl TryFrom<u32> for Age {
    type Err = MyError;
    fn try_from(age: u32) -> Result<Age,MyError> {
        if age > 200 {
            Err(MyError::TooOld)
        } else {
            Ok(Age(age))
        }
    }
}

#[derive(Default)]
struct Mail(String);

// We should probably have TryFrom<&str> here
impl TryFrom<String> for Mail {
    type Err = MyError;
    fn try_from(mail: String) -> Result<Mail,MyError>{
        // Parsing goes here
        Ok(Mail(mail.into()))
    }
}

fn main() {

    let _person: Result<Person, MyError> = PersonBuilder::default()
        .name("bob".to_string())
        .age(25)
        .mail("[email protected]".to_string())
        .build();
}

Here is the code that could be derived behind the scenes

struct PersonBuilder {
    inner: Result<Person, MyError>
}

impl PersonBuilder {
    fn name(mut self, name: String) -> PersonBuilder {
        if let &mut  Ok(ref mut inner) = &mut self.inner {
            inner.name = name
        };
        self
    }

    // This code can probably be prettyfied
    fn age<T: TryInto<Age, Err=MyError>>(mut self, age: T)  -> PersonBuilder {
        match age.try_into() {
            Err(e) => if self.inner.is_ok() {
                self.inner = Err(e)
            },
            Ok(v) => if let &mut Ok(ref mut inner) = &mut self.inner {
                inner.age = v
            }
        };
        self
    }


    fn mail<T: TryInto<Mail, Err=MyError>>(mut self, mail: T) -> PersonBuilder {
        match mail.try_into() {
            Err(e) => if self.inner.is_ok() {
                self.inner = Err(e)
            },
            Ok(v) => if let &mut Ok(ref mut inner) = &mut self.inner {
                inner.mail = v
            }
        };
        self
    }

    fn build(self) -> Result<Person,MyError> {
        self.inner
    }
}

impl Default for PersonBuilder {
    fn default() -> PersonBuilder {
        PersonBuilder{
            inner: Ok(Person::default())
        }
    }
}

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions