Search icon CANCEL
Subscription
0
Cart icon
Your Cart (0 item)
Close icon
You have no products in your basket yet
Save more on your purchases! discount-offer-chevron-icon
Savings automatically calculated. No voucher code required.
Arrow left icon
All Products
Best Sellers
New Releases
Books
Videos
Audiobooks
Learning Hub
Newsletter Hub
Free Learning
Arrow right icon
timer SALE ENDS IN
0 Days
:
00 Hours
:
00 Minutes
:
00 Seconds
Arrow up icon
GO TO TOP
Rust Standard Library Cookbook

You're reading from   Rust Standard Library Cookbook Over 75 recipes to leverage the power of Rust

Arrow left icon
Product type Paperback
Published in Mar 2018
Publisher Packt
ISBN-13 9781788623926
Length 360 pages
Edition 1st Edition
Languages
Arrow right icon
Authors (2):
Arrow left icon
Jan Hohenheim Jan Hohenheim
Author Profile Icon Jan Hohenheim
Jan Hohenheim
Daniel Durante Daniel Durante
Author Profile Icon Daniel Durante
Daniel Durante
Arrow right icon
View More author details
Toc

Table of Contents (17) Chapters Close

Title Page
Copyright and Credits
Packt Upsell
Contributors
Preface
1. Learning the Basics 2. Working with Collections FREE CHAPTER 3. Handling Files and the Filesystem 4. Serialization 5. Advanced Data Structures 6. Handling Errors 7. Parallelism and Rayon 8. Working with Futures 9. Networking 10. Using Experimental Nightly Features 1. Other Books You May Enjoy Index

Using the builder pattern


Sometimes you need something between the customization of the constructor and the implicitness of the default implementation. Enter the builder pattern, another technique frequently used by the Rust standard library, as it allows a caller to fluidly chain together configurations that they care about and lets them ignore details that they don't care about.

How to do it...

  1. In the src/bin folder, create a file called builder.rs

  2. Add all of the following code and run it with cargo run --bin builder:

1  fn main() {
2    // We can easily create different configurations
3    let normal_burger = BurgerBuilder::new().build();
4    let cheese_burger = BurgerBuilder::new()
       .cheese(true)
       .salad(false)
       .build();
5    let veggie_bigmac = BurgerBuilder::new()
       .vegetarian(true)
       .patty_count(2)
       .build();
6
7    if let Ok(normal_burger) = normal_burger {
8      normal_burger.print();
9    }
10   if let Ok(cheese_burger) = cheese_burger {
11     cheese_burger.print();
12   }
13   if let Ok(veggie_bigmac) = veggie_bigmac {
14     veggie_bigmac.print();
15   }
16
17   // Our builder can perform a check for
18   // invalid configurations
19   let invalid_burger = BurgerBuilder::new()
       .vegetarian(true)
       .bacon(true)
       .build();
20   if let Err(error) = invalid_burger {
21     println!("Failed to print burger: {}", error);
22   }
23
24   // If we omit the last step, we can reuse our builder
25   let cheese_burger_builder = BurgerBuilder::new().cheese(true);
26   for i in 1..10 {
27     let cheese_burger = cheese_burger_builder.build();
28     if let Ok(cheese_burger) = cheese_burger {
29       println!("cheese burger number {} is ready!", i);
30       cheese_burger.print();
31     }
32   }
33 }

This is the configurable object:

35 struct Burger {
36    patty_count: i32,
37    vegetarian: bool,
38    cheese: bool,
39    bacon: bool,
40    salad: bool,
41 }
42 impl Burger {
43    // This method is just here for illustrative purposes
44    fn print(&self) {
45        let pretty_patties = if self.patty_count == 1 {
46            "patty"
47        } else {
48            "patties"
49        };
50        let pretty_bool = |val| if val { "" } else { "no " };
51        let pretty_vegetarian = if self.vegetarian { "vegetarian " 
           }
          else { "" };
52        println!(
53            "This is a {}burger with {} {}, {}cheese, {}bacon and
              {}salad",
54            pretty_vegetarian,
55            self.patty_count,
56            pretty_patties,
57            pretty_bool(self.cheese),
58            pretty_bool(self.bacon),
59            pretty_bool(self.salad)
60        )
61    }
62 }

And this is the builder itself. It is used to configure and create a Burger:

64  struct BurgerBuilder {
65    patty_count: i32,
66    vegetarian: bool,
67    cheese: bool,
68    bacon: bool,
69    salad: bool,
70  }
71  impl BurgerBuilder {
72    // in the constructor, we can specify
73    // the standard values
74    fn new() -> Self {
75      BurgerBuilder {
76        patty_count: 1,
77        vegetarian: false,
78        cheese: false,
79        bacon: false,
80        salad: true,
81      }
82    }
83
84    // Now we have to define a method for every
85    // configurable value
86    fn patty_count(mut self, val: i32) -> Self {
87      self.patty_count = val;
88      self
89    }
90
91    fn vegetarian(mut self, val: bool) -> Self {
92      self.vegetarian = val;
93      self
94    }
95    fn cheese(mut self, val: bool) -> Self {
96      self.cheese = val;
97      self
98    }
99    fn bacon(mut self, val: bool) -> Self {
100     self.bacon = val;
101     self
102   }
103   fn salad(mut self, val: bool) -> Self {
104     self.salad = val;
105     self
106   }
107
108   // The final method actually constructs our object
109   fn build(&self) -> Result<Burger, String> {
110     let burger = Burger {
111       patty_count: self.patty_count,
112       vegetarian: self.vegetarian,
113       cheese: self.cheese,
114       bacon: self.bacon,
115       salad: self.salad,
116   };
117   // Check for invalid configuration
118   if burger.vegetarian && burger.bacon {
119     Err("Sorry, but we don't server vegetarian bacon
             yet".to_string())
120     } else {
121       Ok(burger)
122     }
123   }
124 }

How it works...

Whew, that's a lot of code! Let's start by breaking it up.

In the first part, we illustrate how to use this pattern to effortlessly configure a complex object. We do this by relying on sensible standard values and only specifying what we really care about:

let normal_burger = BurgerBuilder::new().build();
let cheese_burger = BurgerBuilder::new()
    .cheese(true)
    .salad(false)
    .build();
let veggie_bigmac = BurgerBuilder::new()
    .vegetarian(true)
    .patty_count(2)
    .build();

The code reads pretty nicely, doesn't it?

In our version of the builder pattern, we return the object wrapped in a Result in order to tell the world that there are certain invalid configurations and that our builder might not always be able to produce a valid product. Because of this, we have to check the validity of our burger before accessing it[7, 10 and 13].

Our invalid configuration is vegetarian(true) and bacon(true). Unfortunately, our restaurant doesn't serve vegetarian bacon yet! When you start the program, you will see that the following line will print an error:

if let Err(error) = invalid_burger {
    println!("Failed to print burger: {}", error);
}

If we omit the final build step, we can reuse the builder in order to build as many objects as we want. [25 to 32]

Let's see how we implemented all of this. The first thing after the main function is the definition of our Burger struct. No surprises here, it's just plain old data. The print method is just here to provide us with some nice output during runtime. You can ignore it if you want.

The real logic is in the BurgerBuilder[64]. It should have one member for every value you want to configure. As we want to configure every aspect of our burger, we will have the exact same members as Burger. In the constructor [74], we can specify some default values. We then create one method for every configuration. In the end, in build() [109], we first perform some error checking. If the configuration is OK, we return a Burger made out of all of our members [121]. Otherwise, we return an error [119].

There's more...

If you want your object to be constructable without a builder, you could also provide Burger with a Default implementation. BurgerBuilder::new() could then just return Default::default().

In build(), if your configuration can inherently not be invalid, you can, of course, return the object directly without wrapping it in a Result.

lock icon The rest of the chapter is locked
Register for a free Packt account to unlock a world of extra content!
A free Packt account unlocks extra newsletters, articles, discounted offers, and much more. Start advancing your knowledge today.
Unlock this book and the full library FREE for 7 days
Get unlimited access to 7000+ expert-authored eBooks and videos courses covering every tech area you can think of
Renews at ₹800/month. Cancel anytime
Visually different images