https://rust-unofficial.github.io/patterns/patterns/creational/builder.html
Construct an object with calls to a builder helper.
I guess this Pasta example is a bad example… need to be improved
Suppose you define a new struct (I use &str
because it is just a snippet, but in real code, you should use String
in this case):
struct Pasta<'a> {
name: &'a str,
tomato: bool,
garlic: bool,
special_order: &'a str
}
You can create/initialize an object like below:
let pasta_obj = Pasta {
name: "Margherita",
tomato: true,
garlic: false,
special_order: "less-boiled"
}
But if Pasta
has a lot of fields, the line would be long.
Moreover, if you want to calculate values before fields are set, you need to create a function which takes Pasta
object as a parameter.
(Otherwise, code would be long and unreadable.)
Think about the following code:
let pasta_obj = PastaBuilder::new()
.template("Margherita") // it will set all required fields.
Once you write template
function, this code is readable, and the responsibility of creation is moved to that function.
Moreover, this code would be more convenient (fluent interface):
let pasta_obj = PastaBuilder::new()
.template("Margherita")
.cheese("Emmental")
To achieve this convenience, one can introduce pasta-builder.
struct PastaBuilder<'a> {
boul: Vec<&'a str>,
knife: bool,
}
impl<'a> PastaBuilder<'a> {
fn new() -> Self {
Self {
boul: vec![],
knife: true
}
}
fn add(self, vegetable: &'a str) -> Self {
self.boul.push(vegetable);
self
}
fn build(self) -> Pasta<'a> {
// cooking with boul and knife
// complex cooking method
Pasta {
// builded pasta object
}
}
}
You can regard this PastaBuilder
as a helper to create Pasta
instance.
https://rust-unofficial.github.io/patterns/patterns/creational/builder.html#advantages
std
As another example, std::process::Command
is a builder for std::process::Child
, and you can append arguments as many times you want:
use std::process::Command;
let output = if cfg!(target_os = "windows") {
Command::new("cmd")
.args(["/C", "echo hello"])
.output()
.expect("failed to execute process")
} else {
Command::new("sh")
.arg("-c")
.arg("echo hello")
.output()
.expect("failed to execute process")
};
let hello = output.stdout;
If the constructor is simple, you can simply implement constructor for the struct.
https://rust-unofficial.github.io/patterns/idioms/ctor.html
Default
trait (constructor) is useful in this case.
https://rust-unofficial.github.io/patterns/patterns/creational/builder.html#discussion
Rust lacks overloading (by design). Since you can only have a single method with a given name, having multiple constructors is less nice in Rust than in C++, Java, or others.
https://users.rust-lang.org/t/is-there-a-simple-way-to-overload-functions/30937/3
Because function overloading is conceptually a missconception. Logically if we assume that function name is describing a behaviour, there is no reason, that the same behaviour may be performed on two completelly independent, not simillar types - which is what overloading actually is. If there is a situation, where operation may be performed on set of types, its clear, that they have something in common, so this commoness shouold be extracted to trait, and then generalizing function over trait is straight way to go.
Once you understand the builder pattern, you can also understand typestate programming in Rust.
https://docs.rust-embedded.org/book/static-guarantees/typestate-programming.html