1. 18
  1.  

  2. 11

    Rust is the only language I’ve actually liked using the builder pattern in. Because of the way mutability works in Rust, you don’t need that last freeze/build step. For example:

    let person = Person::new()
                        .name("Bob")
                        .age(30);
    

    This works because the methods .name() and .age() take the Person object as mutable, make their changes, and return the object. When the rhs is done evaluating, it gets bound to an immutable symbol, so the object is, essentially, “frozen”. This doesn’t have to be limited to single-expression use either, as you can shadow existing symbols and change their mutability:

    let mut person = Person::new();
    
    // get name some time later
    
    let person = person.name("Alice");
    

    During the period between the let mut person and the let person, the object is mutable, but as soon as we are done constructing it, we “freeze” it and make it immutable. Here is the implementation that makes this work:

    struct Person {
        name: String,
        age: u32,
    }
    
    impl Person {
        // fill in with default values
        pub fn new() -> Person {
            Person {
                name: "".to_string(),
                age: 0,
            }
        }
    
        pub fn name<S: Into<String>>(mut self, name: S) -> Person {
            self.name = name.into();
            self
        }
    
        pub fn age(mut self, age: u32) -> Person {
            self.age = age;
            self
        }
    }
    
    1. 2

      So can you not have an immutable reference to a mutable instance in Rust? Because that’s something I sometimes want.

      1. 2

        sure you can:

        // Won't compile because immut_person takes `p` as immutable
        struct Person {
            pub name: String,
            age: u32,
        }
        
        fn immut_person(p: &Person) {
            p.name = "whoops!".to_string();
        }
        
        fn main() {
            let mut person = Person {
                name: "Bob".to_string(),
                age: 30,
            };
        
            immut_person(&person);
        }
        

        (link to rust playground: https://is.gd/6S5AVJ or full link if you’re (understandably) uncomfortable with shortened links: https://play.rust-lang.org/?code=%2F%2F%20Won%27t%20compile%20because%20immut_person%20takes%20%60p%60%20as%20immutable%0Astruct%20Person%20%7B%0A%20%20%20%20pub%20name%3A%20String%2C%0A%20%20%20%20age%3A%20u32%2C%0A%7D%0A%0Afn%20immut_person(p%3A%20%26Person)%20%7B%0A%20%20%20%20p.name%20%3D%20%22whoops!%22.tostring()%3B%0A%7D%0A%0Afn%20main()%20%7B%0A%20%20%20%20let%20mut%20person%20%3D%20Person%20%7B%0A%20%20%20%20%20%20%20%20name%3A%20%22Bob%22.tostring()%2C%0A%20%20%20%20%20%20%20%20age%3A%2030%2C%0A%20%20%20%20%7D%3B%0A%20%20%20%20%0A%20%20%20%20immut_person(%26person)%3B%0A%7D%0A&version=stable&backtrace=0 )

        1. 2

          That’s not what I meant. I want person to be immutable in the sense that it will always refer to the same instance, but I want that instance to be mutable.

          1. 3

            Yes, you can. Just bind person to a mutable borrow and pass that along.

            fn immut_person(p: &mut Person) {
                p.name = "whoops!".to_string();
            }
            
            fn main() {
                let person = &mut Person {
                    name: "Bob".to_string(),
                    age: 30,
                };
            
                immut_person(person);
            }
            

            another

            person = &mut Person {
                    name: "Foo".to_string(),
                    age: 30,
            };
            

            Wouldn’t work, as person is already bound. Please note that another let person would introduce a new binding, so that would work.

      2. [Comment removed by author]

        1. 2

          Well typically in a rust program, values that are truly optional, that could be set to null in another language, are of type Option<T>, which is an enum with 2 variants, Some(T) and None. So if Person had some values that were truly optional, you could wrap their type in an Option and set them to None initially. I’m not doing that here, I’m setting some default values in the new() method, and overwriting them with the name() and age() calls. If this pattern doesn’t work for some reason (like, there is no good default that you can set the values to in the new method) then maybe the builder pattern isn’t right for this occasion, or maybe you do need a final “build” step to make sure that all the values are “good” values. In this latter case though, I would tend to avoid the builder pattern and use something less error-prone.

          In my example above, here is how the Person struct looks, from call-to-call:

          let person = Person::new()
          

          at this point, the value is

          Person {
              name: "",
              age: 0,
          }
          

          because those are the “default” values I set in the new() method.

          let person = Person::new()
                                .name("Bob")
          

          at this point, the value is

          Person {
              name: "Bob",
              age: 0,
          }
          

          Finally,

          let person = Person::new()
                                .name("Bob")
                                .age(30);
          

          At this point the struct is completely constructed, and looks like this:

          Person {
              name: "Bob",
              age: 30,
          }
          

          Note that I say it is “completely constructed,” but that is not quite true, as it was “completely constructed” after the call to ::new(), it just didn’t have the values in it that we wanted it to have. In this case, when I say “completely constructed” I mean “ready for us to use.”

          1. 2

            If values are not optional, they should go in the constructor of the builder. Problem solved.

            1. 2

              Do you mean passed as values to the constructor of the builder? I think that’s half right. I think “If values are not optional and have no reasonable default, they should go in the constructor of the builder” is more complete. A value that is required, but has a sensible default, can be set in the body of the constructor of the builder and the user shouldn’t have to worry about it.

              1. 2

                I agree with your more-complete wording.

                Things which have sensible defaults are fine to restrict to a builder-style fluent interface IMO.

        2. 2

          The example seems a bit contrived, why would you go for .enableFoo() instead of .foo(true) ? Then it is very comparable to the map ..

          1. 1

            I totally agree, sometimes the easier solution is also just better.