]>
Commit | Line | Data |
---|---|---|
1a4d82fc JJ |
1 | % Method Syntax |
2 | ||
3 | Functions are great, but if you want to call a bunch of them on some data, it | |
4 | can be awkward. Consider this code: | |
5 | ||
bd371182 | 6 | ```rust,ignore |
62682a34 | 7 | baz(bar(foo)); |
1a4d82fc JJ |
8 | ``` |
9 | ||
bd371182 AL |
10 | We would read this left-to right, and so we see ‘baz bar foo’. But this isn’t the |
11 | order that the functions would get called in, that’s inside-out: ‘foo bar baz’. | |
12 | Wouldn’t it be nice if we could do this instead? | |
1a4d82fc | 13 | |
bd371182 | 14 | ```rust,ignore |
9346a6ac | 15 | foo.bar().baz(); |
1a4d82fc JJ |
16 | ``` |
17 | ||
18 | Luckily, as you may have guessed with the leading question, you can! Rust provides | |
bd371182 | 19 | the ability to use this ‘method call syntax’ via the `impl` keyword. |
85aaf69f | 20 | |
bd371182 | 21 | # Method calls |
1a4d82fc | 22 | |
bd371182 | 23 | Here’s how it works: |
1a4d82fc | 24 | |
bd371182 | 25 | ```rust |
1a4d82fc JJ |
26 | struct Circle { |
27 | x: f64, | |
28 | y: f64, | |
29 | radius: f64, | |
30 | } | |
31 | ||
32 | impl Circle { | |
33 | fn area(&self) -> f64 { | |
34 | std::f64::consts::PI * (self.radius * self.radius) | |
35 | } | |
36 | } | |
37 | ||
38 | fn main() { | |
39 | let c = Circle { x: 0.0, y: 0.0, radius: 2.0 }; | |
40 | println!("{}", c.area()); | |
41 | } | |
42 | ``` | |
43 | ||
44 | This will print `12.566371`. | |
45 | ||
bd371182 AL |
46 | |
47 | ||
48 | We’ve made a struct that represents a circle. We then write an `impl` block, | |
49 | and inside it, define a method, `area`. | |
50 | ||
51 | Methods take a special first parameter, of which there are three variants: | |
52 | `self`, `&self`, and `&mut self`. You can think of this first parameter as | |
53 | being the `foo` in `foo.bar()`. The three variants correspond to the three | |
54 | kinds of things `foo` could be: `self` if it’s just a value on the stack, | |
55 | `&self` if it’s a reference, and `&mut self` if it’s a mutable reference. | |
56 | Because we took the `&self` parameter to `area`, we can use it just like any | |
57 | other parameter. Because we know it’s a `Circle`, we can access the `radius` | |
58 | just like we would with any other struct. | |
59 | ||
60 | We should default to using `&self`, as you should prefer borrowing over taking | |
61 | ownership, as well as taking immutable references over mutable ones. Here’s an | |
62 | example of all three variants: | |
c34b1796 AL |
63 | |
64 | ```rust | |
65 | struct Circle { | |
66 | x: f64, | |
67 | y: f64, | |
68 | radius: f64, | |
69 | } | |
70 | ||
71 | impl Circle { | |
72 | fn reference(&self) { | |
73 | println!("taking self by reference!"); | |
74 | } | |
75 | ||
76 | fn mutable_reference(&mut self) { | |
77 | println!("taking self by mutable reference!"); | |
78 | } | |
79 | ||
80 | fn takes_ownership(self) { | |
81 | println!("taking ownership of self!"); | |
82 | } | |
83 | } | |
84 | ``` | |
1a4d82fc | 85 | |
bd371182 | 86 | # Chaining method calls |
85aaf69f SL |
87 | |
88 | So, now we know how to call a method, such as `foo.bar()`. But what about our | |
62682a34 SL |
89 | original example, `foo.bar().baz()`? This is called ‘method chaining’. Let’s |
90 | look at an example: | |
85aaf69f | 91 | |
62682a34 | 92 | ```rust |
85aaf69f SL |
93 | struct Circle { |
94 | x: f64, | |
95 | y: f64, | |
96 | radius: f64, | |
97 | } | |
98 | ||
99 | impl Circle { | |
100 | fn area(&self) -> f64 { | |
101 | std::f64::consts::PI * (self.radius * self.radius) | |
102 | } | |
103 | ||
c34b1796 AL |
104 | fn grow(&self, increment: f64) -> Circle { |
105 | Circle { x: self.x, y: self.y, radius: self.radius + increment } | |
85aaf69f SL |
106 | } |
107 | } | |
108 | ||
109 | fn main() { | |
110 | let c = Circle { x: 0.0, y: 0.0, radius: 2.0 }; | |
111 | println!("{}", c.area()); | |
112 | ||
c34b1796 | 113 | let d = c.grow(2.0).area(); |
85aaf69f SL |
114 | println!("{}", d); |
115 | } | |
116 | ``` | |
117 | ||
118 | Check the return type: | |
119 | ||
62682a34 | 120 | ```rust |
85aaf69f SL |
121 | # struct Circle; |
122 | # impl Circle { | |
123 | fn grow(&self) -> Circle { | |
124 | # Circle } } | |
125 | ``` | |
126 | ||
bd371182 | 127 | We just say we’re returning a `Circle`. With this method, we can grow a new |
c34b1796 | 128 | circle to any arbitrary size. |
85aaf69f | 129 | |
bd371182 | 130 | # Associated functions |
85aaf69f | 131 | |
bd371182 AL |
132 | You can also define associated functions that do not take a `self` parameter. |
133 | Here’s a pattern that’s very common in Rust code: | |
1a4d82fc | 134 | |
bd371182 | 135 | ```rust |
1a4d82fc JJ |
136 | struct Circle { |
137 | x: f64, | |
138 | y: f64, | |
139 | radius: f64, | |
140 | } | |
141 | ||
142 | impl Circle { | |
143 | fn new(x: f64, y: f64, radius: f64) -> Circle { | |
144 | Circle { | |
145 | x: x, | |
146 | y: y, | |
147 | radius: radius, | |
148 | } | |
149 | } | |
150 | } | |
151 | ||
152 | fn main() { | |
153 | let c = Circle::new(0.0, 0.0, 2.0); | |
154 | } | |
155 | ``` | |
156 | ||
bd371182 AL |
157 | This ‘associated function’ builds a new `Circle` for us. Note that associated |
158 | functions are called with the `Struct::function()` syntax, rather than the | |
62682a34 | 159 | `ref.method()` syntax. Some other languages call associated functions ‘static |
bd371182 | 160 | methods’. |
1a4d82fc | 161 | |
bd371182 | 162 | # Builder Pattern |
85aaf69f | 163 | |
bd371182 | 164 | Let’s say that we want our users to be able to create Circles, but we will |
85aaf69f | 165 | allow them to only set the properties they care about. Otherwise, the `x` |
bd371182 | 166 | and `y` attributes will be `0.0`, and the `radius` will be `1.0`. Rust doesn’t |
85aaf69f SL |
167 | have method overloading, named arguments, or variable arguments. We employ |
168 | the builder pattern instead. It looks like this: | |
169 | ||
62682a34 | 170 | ```rust |
85aaf69f SL |
171 | struct Circle { |
172 | x: f64, | |
173 | y: f64, | |
174 | radius: f64, | |
175 | } | |
176 | ||
177 | impl Circle { | |
178 | fn area(&self) -> f64 { | |
179 | std::f64::consts::PI * (self.radius * self.radius) | |
180 | } | |
181 | } | |
182 | ||
183 | struct CircleBuilder { | |
c34b1796 AL |
184 | x: f64, |
185 | y: f64, | |
85aaf69f SL |
186 | radius: f64, |
187 | } | |
188 | ||
189 | impl CircleBuilder { | |
190 | fn new() -> CircleBuilder { | |
bd371182 | 191 | CircleBuilder { x: 0.0, y: 0.0, radius: 1.0, } |
85aaf69f SL |
192 | } |
193 | ||
c34b1796 AL |
194 | fn x(&mut self, coordinate: f64) -> &mut CircleBuilder { |
195 | self.x = coordinate; | |
196 | self | |
197 | } | |
198 | ||
199 | fn y(&mut self, coordinate: f64) -> &mut CircleBuilder { | |
9346a6ac | 200 | self.y = coordinate; |
c34b1796 | 201 | self |
85aaf69f SL |
202 | } |
203 | ||
204 | fn radius(&mut self, radius: f64) -> &mut CircleBuilder { | |
c34b1796 AL |
205 | self.radius = radius; |
206 | self | |
85aaf69f SL |
207 | } |
208 | ||
209 | fn finalize(&self) -> Circle { | |
c34b1796 | 210 | Circle { x: self.x, y: self.y, radius: self.radius } |
85aaf69f SL |
211 | } |
212 | } | |
213 | ||
214 | fn main() { | |
215 | let c = CircleBuilder::new() | |
c34b1796 AL |
216 | .x(1.0) |
217 | .y(2.0) | |
218 | .radius(2.0) | |
85aaf69f SL |
219 | .finalize(); |
220 | ||
85aaf69f | 221 | println!("area: {}", c.area()); |
c34b1796 AL |
222 | println!("x: {}", c.x); |
223 | println!("y: {}", c.y); | |
85aaf69f SL |
224 | } |
225 | ``` | |
226 | ||
bd371182 AL |
227 | What we’ve done here is make another struct, `CircleBuilder`. We’ve defined our |
228 | builder methods on it. We’ve also defined our `area()` method on `Circle`. We | |
85aaf69f | 229 | also made one more method on `CircleBuilder`: `finalize()`. This method creates |
bd371182 | 230 | our final `Circle` from the builder. Now, we’ve used the type system to enforce |
85aaf69f SL |
231 | our concerns: we can use the methods on `CircleBuilder` to constrain making |
232 | `Circle`s in any way we choose. |