]> git.proxmox.com Git - rustc.git/blame - src/doc/style/ownership/builders.md
Imported Upstream version 1.2.0+dfsg1
[rustc.git] / src / doc / style / ownership / builders.md
CommitLineData
85aaf69f
SL
1% The builder pattern
2
3Some data structures are complicated to construct, due to their construction needing:
4
5* a large number of inputs
6* compound data (e.g. slices)
7* optional configuration data
8* choice between several flavors
9
10which can easily lead to a large number of distinct constructors with
11many arguments each.
12
13If `T` is such a data structure, consider introducing a `T` _builder_:
14
151. Introduce a separate data type `TBuilder` for incrementally configuring a `T`
16 value. When possible, choose a better name: e.g. `Command` is the builder for
17 `Process`.
182. The builder constructor should take as parameters only the data _required_ to
62682a34 19 make a `T`.
85aaf69f
SL
203. The builder should offer a suite of convenient methods for configuration,
21 including setting up compound inputs (like slices) incrementally.
22 These methods should return `self` to allow chaining.
234. The builder should provide one or more "_terminal_" methods for actually building a `T`.
24
25The builder pattern is especially appropriate when building a `T` involves side
bd371182 26effects, such as spawning a thread or launching a process.
85aaf69f
SL
27
28In Rust, there are two variants of the builder pattern, differing in the
29treatment of ownership, as described below.
30
31### Non-consuming builders (preferred):
32
33In some cases, constructing the final `T` does not require the builder itself to
34be consumed. The follow variant on
35[`std::io::process::Command`](http://static.rust-lang.org/doc/master/std/io/process/struct.Command.html)
36is one example:
37
38```rust
39// NOTE: the actual Command API does not use owned Strings;
40// this is a simplified version.
41
42pub struct Command {
43 program: String,
44 args: Vec<String>,
45 cwd: Option<String>,
46 // etc
47}
48
49impl Command {
50 pub fn new(program: String) -> Command {
51 Command {
52 program: program,
53 args: Vec::new(),
54 cwd: None,
55 }
56 }
57
58 /// Add an argument to pass to the program.
59 pub fn arg<'a>(&'a mut self, arg: String) -> &'a mut Command {
60 self.args.push(arg);
61 self
62 }
63
64 /// Add multiple arguments to pass to the program.
65 pub fn args<'a>(&'a mut self, args: &[String])
66 -> &'a mut Command {
67 self.args.push_all(args);
68 self
69 }
70
71 /// Set the working directory for the child process.
72 pub fn cwd<'a>(&'a mut self, dir: String) -> &'a mut Command {
73 self.cwd = Some(dir);
74 self
75 }
76
77 /// Executes the command as a child process, which is returned.
62682a34 78 pub fn spawn(&self) -> std::io::Result<Process> {
85aaf69f
SL
79 ...
80 }
81}
82```
83
84Note that the `spawn` method, which actually uses the builder configuration to
85spawn a process, takes the builder by immutable reference. This is possible
86because spawning the process does not require ownership of the configuration
87data.
88
89Because the terminal `spawn` method only needs a reference, the configuration
90methods take and return a mutable borrow of `self`.
91
92#### The benefit
93
94By using borrows throughout, `Command` can be used conveniently for both
95one-liner and more complex constructions:
96
97```rust
98// One-liners
99Command::new("/bin/cat").arg("file.txt").spawn();
100
101// Complex configuration
102let mut cmd = Command::new("/bin/ls");
103cmd.arg(".");
104
105if size_sorted {
106 cmd.arg("-S");
107}
108
109cmd.spawn();
110```
111
112### Consuming builders:
113
114Sometimes builders must transfer ownership when constructing the final type
115`T`, meaning that the terminal methods must take `self` rather than `&self`:
116
117```rust
bd371182 118// A simplified excerpt from std::thread::Builder
85aaf69f 119
bd371182
AL
120impl ThreadBuilder {
121 /// Name the thread-to-be. Currently the name is used for identification
85aaf69f 122 /// only in failure messages.
bd371182 123 pub fn named(mut self, name: String) -> ThreadBuilder {
85aaf69f
SL
124 self.name = Some(name);
125 self
126 }
127
bd371182
AL
128 /// Redirect thread-local stdout.
129 pub fn stdout(mut self, stdout: Box<Writer + Send>) -> ThreadBuilder {
85aaf69f
SL
130 self.stdout = Some(stdout);
131 // ^~~~~~ this is owned and cannot be cloned/re-used
132 self
133 }
134
bd371182 135 /// Creates and executes a new child thread.
85aaf69f
SL
136 pub fn spawn(self, f: proc():Send) {
137 // consume self
138 ...
139 }
140}
141```
142
143Here, the `stdout` configuration involves passing ownership of a `Writer`,
bd371182 144which must be transferred to the thread upon construction (in `spawn`).
85aaf69f
SL
145
146When the terminal methods of the builder require ownership, there is a basic tradeoff:
147
148* If the other builder methods take/return a mutable borrow, the complex
149 configuration case will work well, but one-liner configuration becomes
150 _impossible_.
151
152* If the other builder methods take/return an owned `self`, one-liners
153 continue to work well but complex configuration is less convenient.
154
155Under the rubric of making easy things easy and hard things possible, _all_
156builder methods for a consuming builder should take and returned an owned
157`self`. Then client code works as follows:
158
159```rust
160// One-liners
bd371182 161ThreadBuilder::new().named("my_thread").spawn(proc() { ... });
85aaf69f
SL
162
163// Complex configuration
bd371182
AL
164let mut thread = ThreadBuilder::new();
165thread = thread.named("my_thread_2"); // must re-assign to retain ownership
85aaf69f
SL
166
167if reroute {
bd371182 168 thread = thread.stdout(mywriter);
85aaf69f
SL
169}
170
bd371182 171thread.spawn(proc() { ... });
85aaf69f
SL
172```
173
174One-liners work as before, because ownership is threaded through each of the
175builder methods until being consumed by `spawn`. Complex configuration,
176however, is more verbose: it requires re-assigning the builder at each step.