Merge pull request #70 from rust-lang/method_syntax

First draft: method syntax
This commit is contained in:
Steve Klabnik 2016-03-24 14:21:15 -04:00
commit 61194c7b8f
3 changed files with 285 additions and 3 deletions

View File

@ -22,7 +22,7 @@
- [Slices](slices.md)
- [Structs](structs.md)
- [Method Syntax]()
- [Method Syntax](method-syntax.md)
- [Generics]()
- [Advanced]()

277
src/method-syntax.md Normal file
View File

@ -0,0 +1,277 @@
# Method Syntax
In the last section on ownership, we made several references to methods.
Methods look like this:
```rust
let s1 = String::from("hello");
// call a method on our String
let s2 = s1.clone();
println!("{}", s1);
```
The call to `clone()` is attatched to `s1` with a dot. This is called method
syntax, and its a way to call certain functions with a different style.
Why have two ways to call functions? Well talk about some deeper reasons
related to ownership in a moment, but one big reason is that methods look nicer
when chained together:
```rust,ignore
// with functions
h(g(f(x)));
// with methods
x.f().g().h();
```
The nested-functions version reads in reverse: we call `f()`, then `g()`, then
`h()`, but it reads as `h()`, then `g()`, then `f()`.
Before we get into the details, lets talk about how to define your own
methods.
## Defining methods
We can define methods with the `impl` keyword. `impl` is short for
implementation. Doing so looks like this:
```rust
#[derive(Debug,Copy,Clone)]
struct Point {
x: f64,
y: f64,
}
impl Point {
fn distance(&self, other: &Point) -> f64 {
let x_squared = f64::powi(other.x - self.x, 2);
let y_squared = f64::powi(other.y - self.y, 2);
f64::sqrt(x_squared + y_squared)
}
}
let p1 = Point { x: 0.0, y: 0.0 };
let p2 = Point { x: 5.0, y: 6.5 };
assert_eq!(8.200609733428363, p1.distance(&p2));
```
Lets break this down. First, we have our `Point` struct from earlier in the
chapter. Next comes our first use of the `impl` keyword:
```
# #[derive(Debug,Copy,Clone)]
# struct Point {
# x: f64,
# y: f64,
# }
#
impl Point {
# fn distance(&self, other: &Point) -> f64 {
# let x_squared = f64::powi(other.x - self.x, 2);
# let y_squared = f64::powi(other.y - self.y, 2);
#
# f64::sqrt(x_squared + y_squared)
# }
}
#
# let p1 = Point { x: 0.0, y: 0.0 };
# let p2 = Point { x: 5.0, y: 6.5 };
#
# assert_eq!(8.200609733428363, p1.distance(&p2));
```
Everything we put inside of the curly braces will be methods implemented on
`Point`.
```
# #[derive(Debug,Copy,Clone)]
# struct Point {
# x: f64,
# y: f64,
# }
#
# impl Point {
fn distance(&self, other: &Point) -> f64 {
# let x_squared = f64::powi(other.x - self.x, 2);
# let y_squared = f64::powi(other.y - self.y, 2);
#
# f64::sqrt(x_squared + y_squared)
}
# }
#
# let p1 = Point { x: 0.0, y: 0.0 };
# let p2 = Point { x: 5.0, y: 6.5 };
#
# assert_eq!(8.200609733428363, p1.distance(&p2));
```
Next is our definition. This looks very similar to our previous definition of
`distance()` as a function:
```rust
# #[derive(Debug,Copy,Clone)]
# struct Point {
# x: f64,
# y: f64,
# }
fn distance(p1: Point, p2: Point) -> f64 {
# let x_squared = f64::powi(p2.x - p1.x, 2);
# let y_squared = f64::powi(p2.y - p1.y, 2);
#
# f64::sqrt(x_squared + y_squared)
# }
```
Other than this, the rest of the example is familliar: an implementation of
`distance()`, and using the method to find an answer.
There are two differences. The first is in the first argument. Instead of a name
and a type, we have written `&self`. This is what distinguishes a method from a
function: using `self` inside of an `impl` block. Because we already know that
we are implementing this method on `Point`, we dont need to write the type of
`self` out. However, we have written `&self`, not only `self`. This is because
we want to take our argument by reference rather than by ownership. In other
words, these two forms are the same:
```rust,ignore
fn foo(self: &Point)
fn foo(&self)
```
Just like any other parameter, you can take `self` in three forms. Heres the
list, with the most common form first:
```rust,ignore
fn foo(&self) // take self by reference
fn foo(&mut self) // take self by mutable reference
fn foo(self) // take self by ownership
```
In this case, we only need a reference. We dont plan on taking ownership, and
we dont need to mutate either point. Taking by reference is by far the most
common form of method, followed by a mutable reference, and then occasionally
by ownership.
### Methods and automatic referencing
Weve left out an important detail. Its in this line of the example:
```
# #[derive(Debug,Copy,Clone)]
# struct Point {
# x: f64,
# y: f64,
# }
#
# impl Point {
# fn distance(&self, other: &Point) -> f64 {
# let x_squared = f64::powi(other.x - self.x, 2);
# let y_squared = f64::powi(other.y - self.y, 2);
#
# f64::sqrt(x_squared + y_squared)
# }
# }
#
# let p1 = Point { x: 0.0, y: 0.0 };
# let p2 = Point { x: 5.0, y: 6.5 };
#
assert_eq!(8.200609733428363, p1.distance(&p2));
```
When we defined `distance()`, we took both `self` and the other argument by
reference. Yet, we needed a `&` for `p2` but not `p1`. What gives?
This feature is called automatic referencing, and calling methods is one
of the few places in Rust that has behavior like this. Heres how it works:
when you call a method with `self.(`, Rust will automatically add in `&`s
or `&mut`s to match the signature. In other words, these three are the same:
```rust
# #[derive(Debug,Copy,Clone)]
# struct Point {
# x: f64,
# y: f64,
# }
#
# impl Point {
# fn distance(&self, other: &Point) -> f64 {
# let x_squared = f64::powi(other.x - self.x, 2);
# let y_squared = f64::powi(other.y - self.y, 2);
#
# f64::sqrt(x_squared + y_squared)
# }
# }
# let p1 = Point { x: 0.0, y: 0.0 };
# let p2 = Point { x: 5.0, y: 6.5 };
p1.distance(&p2);
(&p1).distance(&p2);
Point::distance(&p1, &p2);
```
The first one looks much, much cleaner. Heres another example:
```rust
let mut s = String::from("Hello,");
s.push_str(" world!");
// The above is the same as:
// (&mut s).push_str(" world!");
assert_eq!("Hello, world!", s);
```
Because [`push_str()`] has the following signature:
```rust,ignore
fn push_str(&mut self, string: &str) {
```
[`push_str()`]: http://doc.rust-lang.org/collections/string/struct.String.html#method.push_str
This automatic referencing behavior works because methods have a clear receiver
— the type of `self` — and in most cases its clear given the receiver and name
of a method whether the method is just reading (so needs `&self`), mutating (so
`&mut self`), or consuming (so `self`). The fact that Rust makes borrowing
implicit for method receivers is a big part of making ownership ergonomic in
practice.
## Methods can be called like functions
Furthermore, if we have a method, we can also call it like a function:
```rust
# #[derive(Debug,Copy,Clone)]
# struct Point {
# x: f64,
# y: f64,
# }
#
# impl Point {
# fn distance(&self, other: &Point) -> f64 {
# let x_squared = f64::powi(other.x - self.x, 2);
# let y_squared = f64::powi(other.y - self.y, 2);
#
# f64::sqrt(x_squared + y_squared)
# }
# }
# let p1 = Point { x: 0.0, y: 0.0 };
# let p2 = Point { x: 5.0, y: 6.5 };
let d1 = p1.distance(&p2);
let d2 = Point::distance(&p1, &p2);
assert_eq!(d1, d2);
```
Instead of using `self.(`, we use `Point` and the namespace operator to call it
like a function instead. Because functions do not do the automatic referencing,
we must pass in `&p1` explicitly.
While methods can be called like functions, functions cannot be called like
methods. If the first argument isnt named `self`, it cannot be called like a
method.

View File

@ -257,8 +257,11 @@ inexpensive.
## Clone
But what if we _do_ want to deeply copy the `String`s data, and not just the
`String` itself? Theres a common method for that: `clone()`. Heres an example
of `clone()` in action:
`String` itself? Theres a common method for that: `clone()`. We will discuss
methods in the next section on [`struct`]s, but theyre a common enough feature
in many programming languages that you have probably seen them before.
Heres an example of the `clone()` method in action:
```rust
let s1 = String::from("hello");
@ -267,6 +270,8 @@ let s2 = s1.clone();
println!("{}", s1);
```
[`struct`]: structs.html
This will work just fine. Remember our diagram from before? In this case,
it _is_ doing this: