about summary refs log tree commit diff
diff options
context:
space:
mode:
authorAlex Crichton <alex@alexcrichton.com>2015-01-02 09:22:10 -0800
committerAlex Crichton <alex@alexcrichton.com>2015-01-02 09:22:10 -0800
commit5bd7a78f66e4b60d760c087964df622f79047cbb (patch)
tree30faa81942e766705793ff4ea3bbd30108b429c3
parent4b0e084aa12cbc25bb2314997b65d28fb6a31692 (diff)
parentdaed54d016bd7940d11d2d5c25f7e778ab24810b (diff)
downloadrust-5bd7a78f66e4b60d760c087964df622f79047cbb.tar.gz
rust-5bd7a78f66e4b60d760c087964df622f79047cbb.zip
rollup merge of #19625: mrhota/guide_traits
Nothing major. Clarification, copy-editing, typographical and grammatical consistency
-rw-r--r--src/doc/guide.md232
1 files changed, 110 insertions, 122 deletions
diff --git a/src/doc/guide.md b/src/doc/guide.md
index dba05e60ed1..0a531a11a65 100644
--- a/src/doc/guide.md
+++ b/src/doc/guide.md
@@ -4769,13 +4769,13 @@ enum OptionalFloat64 {
 }
 ```
 
-This is really unfortunate. Luckily, Rust has a feature that gives us a better
-way: generics. Generics are called **parametric polymorphism** in type theory,
-which means that they are types or functions that have multiple forms ("poly"
-is multiple, "morph" is form) over a given parameter ("parametric").
+Such repetition is unfortunate. Luckily, Rust has a feature that gives us a
+better way: **generics**. Generics are called **parametric polymorphism** in
+type theory, which means that they are types or functions that have multiple
+forms over a given parameter ("parametric").
 
-Anyway, enough with type theory declarations, let's check out the generic form
-of `OptionalInt`. It is actually provided by Rust itself, and looks like this:
+Let's see how generics help us escape `OptionalInt`. `Option` is already
+provided in Rust's standard library and looks like this:
 
 ```rust
 enum Option<T> {
@@ -4784,25 +4784,27 @@ enum Option<T> {
 }
 ```
 
-The `<T>` part, which you've seen a few times before, indicates that this is
-a generic data type. Inside the declaration of our enum, wherever we see a `T`,
-we substitute that type for the same type used in the generic. Here's an
-example of using `Option<T>`, with some extra type annotations:
+The `<T>` part, which you've seen a few times before, indicates that this is a
+generic data type. `T` is called a **type parameter**. When we create instances
+of `Option`, we need to provide a concrete type in place of the type
+parameter. For example, if we wanted something like our `OptionalInt`, we would
+need to instantiate an `Option<int>`. Inside the declaration of our enum,
+wherever we see a `T`, we replace it with the type specified (or inferred by the
+the compiler).
 
 ```{rust}
 let x: Option<int> = Some(5i);
 ```
 
-In the type declaration, we say `Option<int>`. Note how similar this looks to
-`Option<T>`. So, in this particular `Option`, `T` has the value of `int`. On
-the right-hand side of the binding, we do make a `Some(T)`, where `T` is `5i`.
-Since that's an `int`, the two sides match, and Rust is happy. If they didn't
-match, we'd get an error:
+In this particular `Option`, `T` has the value of `int`. On the right-hand side
+of the binding, we do make a `Some(T)`, where `T` is `5i`.  Since that's an
+`int`, the two sides match, and Rust is happy. If they didn't match, we'd get an
+error:
 
 ```{rust,ignore}
 let x: Option<f64> = Some(5i);
-// error: mismatched types: expected `core::option::Option<f64>`
-// but found `core::option::Option<int>` (expected f64 but found int)
+// error: mismatched types: expected `core::option::Option<f64>`,
+// found `core::option::Option<int>` (expected f64, found int)
 ```
 
 That doesn't mean we can't make `Option<T>`s that hold an `f64`! They just have to
@@ -4813,8 +4815,6 @@ let x: Option<int> = Some(5i);
 let y: Option<f64> = Some(5.0f64);
 ```
 
-This is just fine. One definition, multiple uses.
-
 Generics don't have to only be generic over one type. Consider Rust's built-in
 `Result<T, E>` type:
 
@@ -4835,20 +4835,20 @@ enum Result<H, N> {
 }
 ```
 
-if we wanted to. Convention says that the first generic parameter should be
-`T`, for 'type,' and that we use `E` for 'error'. Rust doesn't care, however.
+Convention says that the first generic parameter should be `T`, for "type," and
+that we use `E` for "error."
 
-The `Result<T, E>` type is intended to
-be used to return the result of a computation, and to have the ability to
-return an error if it didn't work out. Here's an example:
+The `Result<T, E>` type is intended to be used to return the result of a
+computation and to have the ability to return an error if it didn't work
+out. Here's an example:
 
 ```{rust}
 let x: Result<f64, String> = Ok(2.3f64);
 let y: Result<f64, String> = Err("There was an error.".to_string());
 ```
 
-This particular Result will return an `f64` if there's a success, and a
-`String` if there's a failure. Let's write a function that uses `Result<T, E>`:
+This particular `Result` will return an `f64` upon success and a `String` if
+there's a failure. Let's write a function that uses `Result<T, E>`:
 
 ```{rust}
 fn inverse(x: f64) -> Result<f64, String> {
@@ -4858,17 +4858,18 @@ fn inverse(x: f64) -> Result<f64, String> {
 }
 ```
 
-We don't want to take the inverse of zero, so we check to make sure that we
-weren't passed zero. If we were, then we return an `Err`, with a message. If
-it's okay, we return an `Ok`, with the answer.
+We want to indicate that `inverse(0.0f64)` is undefined or is an erroneous usage
+of the function, so we check to make sure that we weren't passed zero. If we
+were, we return an `Err` with a message. If it's okay, we return an `Ok` with
+the answer.
 
 Why does this matter? Well, remember how `match` does exhaustive matches?
 Here's how this function gets used:
 
 ```{rust}
 # fn inverse(x: f64) -> Result<f64, String> {
-#     if x == 0.0f64 { return Err("x cannot be zero!".to_string()); }
-#     Ok(1.0f64 / x)
+# if x == 0.0f64 { return Err("x cannot be zero!".to_string()); }
+# Ok(1.0f64 / x)
 # }
 let x = inverse(25.0f64);
 
@@ -4889,8 +4890,8 @@ println!("{}", x + 2.0f64); // error: binary operation `+` cannot be applied
 ```
 
 This function is great, but there's one other problem: it only works for 64 bit
-floating point values. What if we wanted to handle 32 bit floating point as
-well? We'd have to write this:
+floating point values. If we wanted to handle 32 bit floating point values we'd
+have to write this:
 
 ```{rust}
 fn inverse32(x: f32) -> Result<f32, String> {
@@ -4900,9 +4901,9 @@ fn inverse32(x: f32) -> Result<f32, String> {
 }
 ```
 
-Bummer. What we need is a **generic function**. Luckily, we can write one!
-However, it won't _quite_ work yet. Before we get into that, let's talk syntax.
-A generic version of `inverse` would look something like this:
+What we need is a **generic function**. We can do that with Rust! However, it
+won't _quite_ work yet. We need to talk about syntax. A first attempt at a
+generic version of `inverse` might look something like this:
 
 ```{rust,ignore}
 fn inverse<T>(x: T) -> Result<T, String> {
@@ -4912,24 +4913,34 @@ fn inverse<T>(x: T) -> Result<T, String> {
 }
 ```
 
-Just like how we had `Option<T>`, we use a similar syntax for `inverse<T>`.
-We can then use `T` inside the rest of the signature: `x` has type `T`, and half
-of the `Result` has type `T`. However, if we try to compile that example, we'll get
-an error:
+Just like how we had `Option<T>`, we use a similar syntax for `inverse<T>`.  We
+can then use `T` inside the rest of the signature: `x` has type `T`, and half of
+the `Result` has type `T`. However, if we try to compile that example, we'll get
+some errors:
 
 ```text
 error: binary operation `==` cannot be applied to type `T`
+     if x == 0.0 { return Err("x cannot be zero!".to_string()); }
+                ^~~~~~~~
+error: mismatched types: expected `_`, found `T` (expected floating-point variable, found type parameter)
+     Ok(1.0 / x)
+              ^
+error: mismatched types: expected `core::result::Result<T, collections::string::String>`, found `core::result::Result<_, _>` (expected type parameter, found floating-point variable)
+     Ok(1.0 / x)
+     ^~~~~~~~~~~
 ```
 
-Because `T` can be _any_ type, it may be a type that doesn't implement `==`,
-and therefore, the first line would be wrong. What do we do?
+The problem is that `T` is unconstrained: it can be _any_ type. It could be a
+`String`, and the expression `1.0 / x` has no meaning if `x` is a `String`. It
+may be a type that doesn't implement `==`, and the first line would be
+wrong. What do we do?
 
-To fix this example, we need to learn about another Rust feature: traits.
+To fix this example, we need to learn about another Rust feature: **traits**.
 
 # Traits
 
-Do you remember the `impl` keyword, used to call a function with method
-syntax?
+Our discussion of **traits** begins with the `impl` keyword. We used it before
+to specify methods.
 
 ```{rust}
 struct Circle {
@@ -4945,8 +4956,8 @@ impl Circle {
 }
 ```
 
-Traits are similar, except that we define a trait with just the method
-signature, then implement the trait for that struct. Like this:
+We define a trait in terms of its methods. We then `impl` a trait `for` a type
+(or many types).
 
 ```{rust}
 struct Circle {
@@ -4966,19 +4977,18 @@ impl HasArea for Circle {
 }
 ```
 
-As you can see, the `trait` block looks very similar to the `impl` block,
-but we don't define a body, just a type signature. When we `impl` a trait,
-we use `impl Trait for Item`, rather than just `impl Item`.
+The `trait` block defines only type signatures. When we `impl` a trait, we use
+`impl Trait for Item`, rather than just `impl Item`.
 
-So what's the big deal? Remember the error we were getting with our generic
-`inverse` function?
+The first of the three errors we got with our generic `inverse` function was
+this:
 
 ```text
 error: binary operation `==` cannot be applied to type `T`
 ```
 
-We can use traits to constrain our generics. Consider this function, which
-does not compile, and gives us a similar error:
+We can use traits to constrain generic type parameters. Consider this function,
+which does not compile, and gives us a similar error:
 
 ```{rust,ignore}
 fn print_area<T>(shape: T) {
@@ -4993,8 +5003,9 @@ error: type `T` does not implement any method in scope named `area`
 ```
 
 Because `T` can be any type, we can't be sure that it implements the `area`
-method. But we can add a **trait constraint** to our generic `T`, ensuring
-that it does:
+method. But we can add a **trait constraint** to our generic `T`, ensuring that
+we can only compile the function if it's called with types which `impl` the
+`HasArea` trait:
 
 ```{rust}
 # trait HasArea {
@@ -5005,9 +5016,9 @@ fn print_area<T: HasArea>(shape: T) {
 }
 ```
 
-The syntax `<T: HasArea>` means `any type that implements the HasArea trait`.
-Because traits define function type signatures, we can be sure that any type
-which implements `HasArea` will have an `.area()` method.
+The syntax `<T: HasArea>` means "any type that implements the HasArea trait."
+Because traits define method signatures, we can be sure that any type which
+implements `HasArea` will have an `area` method.
 
 Here's an extended example of how this works:
 
@@ -5105,55 +5116,22 @@ impl HasArea for int {
 It is considered poor style to implement methods on such primitive types, even
 though it is possible.
 
-This may seem like the Wild West, but there are two other restrictions around
-implementing traits that prevent this from getting out of hand. First, traits
-must be `use`d in any scope where you wish to use the trait's method. So for
-example, this does not work:
-
-```{rust,ignore}
-mod shapes {
-    use std::f64::consts;
-
-    trait HasArea {
-        fn area(&self) -> f64;
-    }
-
-    struct Circle {
-        x: f64,
-        y: f64,
-        radius: f64,
-    }
-
-    impl HasArea for Circle {
-        fn area(&self) -> f64 {
-            consts::PI * (self.radius * self.radius)
-        }
-    }
-}
-
-fn main() {
-    let c = shapes::Circle {
-        x: 0.0f64,
-        y: 0.0f64,
-        radius: 1.0f64,
-    };
+## Scoped Method Resolution and Orphan `impl`s
 
-    println!("{}", c.area());
-}
-```
-
-Now that we've moved the structs and traits into their own module, we get an
-error:
+There are two restrictions for implementing traits that prevent this from
+getting out of hand.
 
-```text
-error: type `shapes::Circle` does not implement any method in scope named `area`
-```
+1. **Scope-based Method Resolution**: Traits must be `use`d in any scope where
+   you wish to use the trait's methods
+2. **No Orphan `impl`s**: Either the trait or the type you're writing the `impl`
+   for must be inside your crate.
 
-If we add a `use` line right above `main` and make the right things public,
-everything is fine:
+If we organize our crate differently by using modules, we'll need to ensure both
+of the conditions are satisfied. Don't worry, you can lean on the compiler since
+it won't let you get away with violating them.
 
 ```{rust}
-use shapes::HasArea;
+use shapes::HasArea; // satisfies #1
 
 mod shapes {
     use std::f64::consts;
@@ -5175,8 +5153,8 @@ mod shapes {
     }
 }
 
-
 fn main() {
+    // use shapes::HasArea; // This would satisfy #1, too
     let c = shapes::Circle {
         x: 0.0f64,
         y: 0.0f64,
@@ -5187,18 +5165,25 @@ fn main() {
 }
 ```
 
-This means that even if someone does something bad like add methods to `int`,
-it won't affect you, unless you `use` that trait.
+Requiring us to `use` traits whose methods we want means that even if someone
+does something bad like add methods to `int`, it won't affect us, unless you
+`use` that trait.
+
+The second condition allows us to `impl` built-in `trait`s for types we define,
+or allows us to `impl` our own `trait`s for built-in types, but restricts us
+from mixing and matching third party or built-in `impl`s with third party or
+built-in types.
 
-There's one more restriction on implementing traits. Either the trait or the
-type you're writing the `impl` for must be inside your crate. So, we could
-implement the `HasArea` type for `int`, because `HasArea` is in our crate.  But
-if we tried to implement `Float`, a trait provided by Rust, for `int`, we could
-not, because both the trait and the type aren't in our crate.
+We could `impl` the `HasArea` trait for `int`, because `HasArea` is in our
+crate. But if we tried to implement `Float`, a standard library `trait`, for
+`int`, we could not, because neither the `trait` nor the `type` are in our
+crate.
 
-One last thing about traits: generic functions with a trait bound use
-**monomorphization** ("mono": one, "morph": form), so they are statically
-dispatched. What's that mean? Well, let's take a look at `print_area` again:
+## Monomorphization
+
+One last thing about generics and traits: the compiler performs
+**monomorphization** on generic functions so they are statically dispatched. To
+see what that means, let's take a look at `print_area` again:
 
 ```{rust,ignore}
 fn print_area<T: HasArea>(shape: T) {
@@ -5215,10 +5200,11 @@ fn main() {
 }
 ```
 
-When we use this trait with `Circle` and `Square`, Rust ends up generating
-two different functions with the concrete type, and replacing the call sites with
-calls to the concrete implementations. In other words, you get something like
-this:
+Because we have called `print_area` with two different types in place of its
+type paramater `T`, Rust will generate two versions of the function with the
+appropriate concrete types, replacing the call sites with calls to the concrete
+implementations. In other words, the compiler will actually compile something
+more like this:
 
 ```{rust,ignore}
 fn __print_area_circle(shape: Circle) {
@@ -5239,10 +5225,12 @@ fn main() {
 }
 ```
 
-The names don't actually change to this, it's just for illustration. But
-as you can see, there's no overhead of deciding which version to call here,
-hence 'statically dispatched'. The downside is that we have two copies of
-the same function, so our binary is a little bit larger.
+These names are for illustration; the compiler will generate its own cryptic
+names for internal uses. The point is that there is no runtime overhead of
+deciding which version to call. The function to be called is determined
+statically, at compile time. Thus, generic functions are **statically
+dispatched**. The downside is that we have two similar functions, so our binary
+is larger.
 
 # Threads