mirror of
https://github.com/rust-lang-cn/book-cn.git
synced 2025-02-02 23:38:41 +08:00
Backport changes to Variable Bindings in Detail section
This commit is contained in:
parent
e094c3bd4e
commit
8bf85b6a58
@ -9,7 +9,7 @@
|
||||
|
||||
- [Up and Running](ch03-01-up-and-running.md)
|
||||
- [Anatomy of a Rust Program](ch03-02-anatomy-of-a-rust-program.md)
|
||||
- [Variable Bindings](ch03-02-variable-bindings.md)
|
||||
- [Variable Bindings in Detail](ch03-03-variable-bindings-in-detail.md)
|
||||
- [Functions](ch03-03-functions.md)
|
||||
- [Scalar Types](ch03-04-scalar-types.md)
|
||||
- [Compound Types](ch03-05-compound-types.md)
|
||||
|
@ -1,638 +0,0 @@
|
||||
# Variable Bindings
|
||||
|
||||
The foundation of virtually every program is the ability to store and modify
|
||||
data. Rust programs are no different. Let’s start with a short example.
|
||||
|
||||
## The basics of bindings
|
||||
|
||||
First, we’ll generate a new project with Cargo. Open a terminal, and navigate
|
||||
to the directory where you’d like to keep your projects. From there, let’s
|
||||
generate a new project:
|
||||
|
||||
```bash
|
||||
$ cargo new --bin bindings
|
||||
$ cd bindings
|
||||
```
|
||||
|
||||
This creates a new project, ‘bindings’, and sets up our `Cargo.toml` and
|
||||
`src/main.rs` files. As we saw in “Hello, World!”, Cargo will generate these
|
||||
files and create a little ‘hello world’ program for us:
|
||||
|
||||
```rust
|
||||
fn main() {
|
||||
println!("Hello, world!");
|
||||
}
|
||||
```
|
||||
|
||||
Let’s replace that program with this one:
|
||||
|
||||
```rust
|
||||
fn main() {
|
||||
let x = 5;
|
||||
|
||||
println!("The value of x is: {}", x);
|
||||
}
|
||||
```
|
||||
|
||||
And finally, run it:
|
||||
|
||||
```bash
|
||||
$ cargo run
|
||||
Compiling bindings v0.1.0 (file:///projects/bindings)
|
||||
Running `target/debug/bindings`
|
||||
The value of x is: 5
|
||||
```
|
||||
|
||||
If you see an error instead, double check that you have copied the program
|
||||
exactly as written. Let’s break this down, line by line.
|
||||
|
||||
```rust,ignore
|
||||
fn main() {
|
||||
```
|
||||
|
||||
The `main()` function is the entry point of every Rust program. We’ll talk more
|
||||
about functions in the next section, but for now, all we need to know is that
|
||||
this is where our program begins. The opening curly brace, `{`, indicates the
|
||||
start of the function’s body.
|
||||
|
||||
```rust,ignore
|
||||
let x = 5;
|
||||
```
|
||||
|
||||
This is our first ‘variable binding’, which we create with a ‘`let` statement’.
|
||||
|
||||
This `let` statement has this form:
|
||||
|
||||
```text
|
||||
let NAME = EXPRESSION;
|
||||
```
|
||||
|
||||
A `let` statement first evaluates the `EXPRESSION`, and then binds the
|
||||
resulting value to `NAME` so that it can be referred to later in the program.
|
||||
In our simple example, the expression was already a value, 5, but we could
|
||||
achieve the same effect with:
|
||||
|
||||
```rust
|
||||
let x = 2 + 3;
|
||||
```
|
||||
|
||||
In general, `let` statements work with patterns; a name is a particularly
|
||||
humble form of pattern. Patterns are a big part of Rust, we’ll see more complex
|
||||
and powerful patterns as we go along.
|
||||
|
||||
Before we do that, though, let’s finish investigating this example. Here’s the
|
||||
next line:
|
||||
|
||||
```rust,ignore
|
||||
println!("The value of x is: {}", x);
|
||||
```
|
||||
|
||||
The `println!` macro prints text to the screen. We can tell that it’s a macro
|
||||
due to the `!`. We won’t learn how to write macros until much later in the
|
||||
book, but we’ll use macros provided by the standard library throughout. Every
|
||||
time you see a `!`, remember that it signifies a macro. Macros can add new
|
||||
syntax to the language, and the `!` is a reminder that things may look slightly
|
||||
unusual.
|
||||
|
||||
`println!`, specifically, has one required argument, a ‘format string’, and
|
||||
zero or more optional arguments. The format string can contain the special text
|
||||
`{}`. Each instance of `{}` corresponds to an additional argument. Here’s an
|
||||
example:
|
||||
|
||||
```rust
|
||||
let x = 2 + 3;
|
||||
let y = x + 5;
|
||||
println!("The value of x is {}, and the value of y is {}", x, y);
|
||||
```
|
||||
|
||||
You can think of `{}` as little crab pincers, holding the value in place. This
|
||||
placeholder has a number of more advanced formatting options that we’ll discuss
|
||||
later.
|
||||
|
||||
```rust,ignore
|
||||
}
|
||||
```
|
||||
|
||||
Finally, a closing curly brace matches up with the opening curly brace that
|
||||
declared the `main()` function, and declares its end.
|
||||
|
||||
This explains our output:
|
||||
|
||||
```text
|
||||
The value of x is: 5
|
||||
```
|
||||
|
||||
We assign `5` to a binding, `x`, and then print it to the screen with
|
||||
`println!`.
|
||||
|
||||
## Multiple binding
|
||||
|
||||
Let’s try a more complex pattern. Change our example program to this:
|
||||
|
||||
```rust
|
||||
fn main() {
|
||||
let (x, y) = (5, 6);
|
||||
|
||||
println!("The value of x is: {}", x);
|
||||
println!("The value of y is: {}", y);
|
||||
}
|
||||
```
|
||||
|
||||
And run it with `cargo run`:
|
||||
|
||||
```text
|
||||
$ cargo run
|
||||
Compiling bindings v0.1.0 (file:///projects/bindings)
|
||||
Running `target/debug/bindings`
|
||||
The value of x is: 5
|
||||
The value of y is: 6
|
||||
```
|
||||
|
||||
We’ve created two bindings with one `let`! Here’s our pattern:
|
||||
|
||||
```text
|
||||
(x, y)
|
||||
```
|
||||
|
||||
And here’s the value:
|
||||
|
||||
```text
|
||||
(5, 6)
|
||||
```
|
||||
|
||||
As you can see, the two line up visually, and so `let` binds `5` to `x` and `6`
|
||||
to `y`. We could have used two `let` statements as well:
|
||||
|
||||
```rust
|
||||
fn main() {
|
||||
let x = 5;
|
||||
let y = 6;
|
||||
}
|
||||
```
|
||||
|
||||
In simple cases like this, two `let`s may be clearer, but in others, creating
|
||||
multiple bindings at once is nice. As we become more proficient in Rust, we’ll
|
||||
figure out which style is better, but it’s mostly a judgement call.
|
||||
|
||||
## Type annotations
|
||||
|
||||
You may have noticed that we didn’t declare the type of `x` or `y` in our
|
||||
previous examples. Rust is a *statically typed* language, which means that at
|
||||
compile time, we must know the types of all bindings. But annotating every
|
||||
single binding with a type can feel like busywork, and make code noisy. To
|
||||
solve this issue, Rust uses ‘type inference’, meaning that it attempts to infer
|
||||
the types of your bindings.
|
||||
|
||||
The primary way that the type is inferred is by looking at how it is used.
|
||||
Let’s look at the example again:
|
||||
|
||||
```rust
|
||||
fn main() {
|
||||
let x = 5;
|
||||
}
|
||||
```
|
||||
|
||||
When we bind `x` to `5`, the compiler knows that `x` should be a numeric type.
|
||||
Without any other information, it defaults to `i32`, a thirty-two bit integer
|
||||
type. We’ll talk more about Rust’s basic types in section 3.3.
|
||||
|
||||
Here’s what a `let` statement with a ‘type annotation’ looks like:
|
||||
|
||||
```rust
|
||||
fn main() {
|
||||
let x: i32 = 5;
|
||||
}
|
||||
```
|
||||
|
||||
We can add a colon, followed by the type name. Here’s the structure of a `let`
|
||||
statement with a type annotation:
|
||||
|
||||
```text
|
||||
let PATTERN: TYPE = VALUE;
|
||||
```
|
||||
|
||||
Note that the colon and the `TYPE` go _after_ the `PATTERN`, not in the pattern
|
||||
itself. As an example, here’s our more complex pattern with two bindings:
|
||||
|
||||
```rust
|
||||
fn main() {
|
||||
let (x, y): (i32, i32) = (5, 6);
|
||||
}
|
||||
```
|
||||
|
||||
Just like we match up the `VALUE` with the `PATTERN`, we match up the `TYPE`
|
||||
with the `PATTERN`.
|
||||
|
||||
## Delayed Initialization
|
||||
|
||||
We do not have to provide bindings with an initial value, and can assign it
|
||||
later. Try this program:
|
||||
|
||||
```rust
|
||||
fn main() {
|
||||
let x;
|
||||
|
||||
x = 5;
|
||||
|
||||
println!("The value of x is: {}", x);
|
||||
}
|
||||
```
|
||||
|
||||
And run it with `cargo run`:
|
||||
|
||||
```text
|
||||
$ cargo run
|
||||
Compiling bindings v0.1.0 (file:///projects/bindings)
|
||||
Running `target/debug/bindings`
|
||||
The value of x is: 5
|
||||
```
|
||||
|
||||
It’s all good. This raises a question, though: what if we try to print out a
|
||||
binding before we declare a value? Here’s a program that demonstrates this
|
||||
question:
|
||||
|
||||
```rust,ignore
|
||||
fn main() {
|
||||
let x;
|
||||
|
||||
println!("The value of x is: {}", x);
|
||||
|
||||
x = 5;
|
||||
}
|
||||
```
|
||||
|
||||
We can find out the answer with `cargo run`:
|
||||
|
||||
```text
|
||||
Compiling bindings v0.1.0 (file:///projects/bindings)
|
||||
src/main.rs:4:39: 4:40 error: use of possibly uninitialized variable: `x` [E0381]
|
||||
src/main.rs:4 println!(“The value of x is: {}”, x);
|
||||
^
|
||||
<std macros>:2:25: 2:56 note: in this expansion of format_args!
|
||||
<std macros>:3:1: 3:54 note: in this expansion of print! (defined in <std macros>)
|
||||
src/main.rs:4:5: 4:42 note: in this expansion of println! (defined in <std macros>)
|
||||
src/main.rs:4:39: 4:40 help: run `rustc --explain E0381` to see a detailed explanation
|
||||
error: aborting due to previous error
|
||||
Could not compile `bindings`.
|
||||
|
||||
To learn more, run the command again with --verbose.
|
||||
```
|
||||
|
||||
An error! The compiler won’t let us write a program like this. This is our
|
||||
first example of the compiler helping us find an error in our program.
|
||||
Different programming languages have different ways of approaching this
|
||||
problem. Some languages always initialize values with some sort of default.
|
||||
Other languages leave the value uninitialized, and make no promises about what
|
||||
happens if you try to use something before initialization. Rust chooses
|
||||
something else: error and force the programmer to explain what they want. We
|
||||
must do some sort of initialization before we can use `x`.
|
||||
|
||||
### Extended error explanations
|
||||
|
||||
There’s one more interesting part of this error message:
|
||||
|
||||
```text
|
||||
src/main.rs:4:39: 4:40 help: run `rustc --explain E0381` to see a detailed explanation
|
||||
```
|
||||
|
||||
We can see an extended explanation by passing the `--explain` flag to `rustc`.
|
||||
Not every error has a longer explanation, but many of them do. These extended
|
||||
explanations try to show off common ways that the error occurs, and common
|
||||
solutions to the issue. Here’s `E0381`:
|
||||
|
||||
```bash
|
||||
$ rustc --explain E0381
|
||||
It is not allowed to use or capture an uninitialized variable. For example:
|
||||
|
||||
fn main() {
|
||||
let x: i32;
|
||||
let y = x; // error, use of possibly uninitialized variable
|
||||
|
||||
To fix this, ensure that any declared variables are initialized before being
|
||||
used.
|
||||
```
|
||||
|
||||
These explanations can really help if you’re stuck on an error. The compiler is
|
||||
your friend, and is here to help.
|
||||
|
||||
## Mutable bindings
|
||||
|
||||
What about changing the value of a binding? Here’s another sample program that
|
||||
asks this question:
|
||||
|
||||
```rust,ignore
|
||||
fn main() {
|
||||
let x = 5;
|
||||
|
||||
x = 6;
|
||||
|
||||
println!("The value of x is: {}", x);
|
||||
}
|
||||
```
|
||||
|
||||
`cargo run` has the answer for us:
|
||||
|
||||
```bash
|
||||
$ cargo run
|
||||
Compiling bindings v0.1.0 (file:///projects/bindings)
|
||||
src/main.rs:4:5: 4:10 error: re-assignment of immutable variable `x` [E0384]
|
||||
src/main.rs:4 x = 6;
|
||||
^~~~~
|
||||
src/main.rs:4:5: 4:10 help: run `rustc --explain E0384` to see a detailed explanation
|
||||
src/main.rs:2:9: 2:10 note: prior assignment occurs here
|
||||
src/main.rs:2 let x = 5;
|
||||
^
|
||||
```
|
||||
|
||||
The error mentions `re-assigment of immutable variable`. That’s right: bindings
|
||||
are immutable. But they’re only immutable by default. In a pattern, when we’re
|
||||
creating a new name, we can add `mut` in front to make the binding a mutable
|
||||
one. Here’s an example:
|
||||
|
||||
```rust
|
||||
fn main() {
|
||||
let mut x = 5;
|
||||
|
||||
println!("The value of x is: {}", x);
|
||||
|
||||
x = 6;
|
||||
|
||||
println!("The value of x is: {}", x);
|
||||
}
|
||||
```
|
||||
|
||||
Running this, we get:
|
||||
|
||||
```bash
|
||||
$ cargo run
|
||||
Compiling bindings v0.1.0 (file:///projects/bindings)
|
||||
Running `target/debug/bindings`
|
||||
The value of x is: 5
|
||||
The value of x is: 6
|
||||
```
|
||||
|
||||
We can now change the value that `x` binds to. Note that the syntax is not `let
|
||||
mut` exactly; it’s using `mut` in a pattern. This becomes more obvious with our
|
||||
`()` pattern:
|
||||
|
||||
|
||||
```rust,ignore
|
||||
fn main() {
|
||||
let (mut x, y) = (5, 6);
|
||||
|
||||
x = 7;
|
||||
y = 8;
|
||||
}
|
||||
```
|
||||
|
||||
The compiler will complain about this program:
|
||||
|
||||
```bash
|
||||
$ cargo build
|
||||
Compiling bindings v0.1.0 (file:///projects/bindings)
|
||||
src/main.rs:5:5: 5:10 error: re-assignment of immutable variable `y` [E0384]
|
||||
src/main.rs:5 y = 8;
|
||||
^~~~~
|
||||
src/main.rs:5:5: 5:10 help: run `rustc --explain E0384` to see a detailed explanation
|
||||
src/main.rs:2:17: 2:18 note: prior assignment occurs here
|
||||
src/main.rs:2 let (mut x, y) = (5, 6);
|
||||
^
|
||||
```
|
||||
|
||||
It’s fine with re-assigning `x`, but not `y`. The `mut` only applies to the
|
||||
name that follows it, not the whole pattern.
|
||||
|
||||
### Reassignment, not mutation
|
||||
|
||||
There is one subtlety we haven’t covered yet: `mut` allows you to mutate _the
|
||||
binding_, but not _what the binding binds to_. In other words:
|
||||
|
||||
```rust
|
||||
fn main() {
|
||||
let mut x = 5;
|
||||
|
||||
x = 6;
|
||||
}
|
||||
```
|
||||
|
||||
This is not changing the value that `x` is bound to, but creating a new value,
|
||||
`6`, and changing the binding to bind to it instead. It’s a subtle but
|
||||
important difference. Well, for now, it does not make a lot of difference, but
|
||||
when our programs get more complex, it will. Specifically, passing arguments to
|
||||
functions will illustrate the difference. We’ll talk about that in the next
|
||||
section, when we discuss functions.
|
||||
|
||||
## Scope
|
||||
|
||||
Variable bindings have a ‘scope’ in which they’re valid. That scope begins from
|
||||
the point at which the binding is declared, and ends at the end of the next
|
||||
block of code. We can only access bindings which are ‘in scope’. We cannot
|
||||
access them ‘before they come into scope’ or ‘after they go out of scope’.
|
||||
Here’s an example:
|
||||
|
||||
```rust
|
||||
fn main() {
|
||||
println!("x is not yet in scope");
|
||||
|
||||
let x = 5;
|
||||
println!("x is now in scope");
|
||||
|
||||
println!("In real code, we’d now do a bunch of work.");
|
||||
|
||||
println!("x will go out of scope now! The next curly brace is ending the main function.");
|
||||
}
|
||||
```
|
||||
|
||||
We can create arbitrary scopes through the use of `{` and `}`:
|
||||
|
||||
```rust
|
||||
fn main() {
|
||||
println!("x is not yet in scope");
|
||||
|
||||
let x = 5;
|
||||
println!("x is now in scope");
|
||||
|
||||
println!("Let’s start a new scope!");
|
||||
|
||||
{
|
||||
let y = 5;
|
||||
println!("y is now in scope");
|
||||
println!("x is also still in scope");
|
||||
|
||||
println!("y will go out of scope now!");
|
||||
println!("The next curly brace is ending the scope we started.");
|
||||
}
|
||||
|
||||
println!("x is still in scope, but y is now out of scope and is not usable");
|
||||
|
||||
println!("x will go out of scope now! The next curly brace is ending the main function.");
|
||||
}
|
||||
```
|
||||
|
||||
What bindings are in and out of scope will become much more important later,
|
||||
once we learn about ‘references’ and ‘traits’.
|
||||
|
||||
## Shadowing
|
||||
|
||||
A final thing about bindings: they can ‘shadow’ previous bindings with the same
|
||||
name. Here’s a sample program:
|
||||
|
||||
```rust
|
||||
fn main() {
|
||||
let x = 5;
|
||||
let x = 6;
|
||||
|
||||
println!("The value of x is: {}", x);
|
||||
}
|
||||
```
|
||||
|
||||
Running it, we can see the shadowing in action:
|
||||
|
||||
```text
|
||||
src/main.rs:2:9: 2:10 warning: unused variable: `x`, #[warn(unused_variables)] on by default
|
||||
src/main.rs:2 let x = 5;
|
||||
^
|
||||
Running `target/debug/bindings`
|
||||
The value of x is: 6
|
||||
```
|
||||
|
||||
There are two interesting things in this output. First, Rust will compile and
|
||||
run this program, no problem. And as we can see, the value of `x` is `6`. But
|
||||
we didn’t declare `x` as mutable. Instead, we declared a _new_ binding that is
|
||||
_also_ named `x`, and gave it a new value. The older value that we bound `x` to
|
||||
is inaccessible as soon as the new `x` is declared. This can be useful if you’d
|
||||
like to perform a few transformations on a value, and leave it immutable. For
|
||||
example:
|
||||
|
||||
```rust
|
||||
fn main() {
|
||||
let x = 5;
|
||||
let x = x + 1;
|
||||
let x = x * 2;
|
||||
|
||||
println!("The value of x is: {}", x);
|
||||
}
|
||||
```
|
||||
|
||||
This will print:
|
||||
|
||||
```bash
|
||||
Compiling bindings v0.1.0 (file:///projects/bindings)
|
||||
Running `target/debug/bindings`
|
||||
The value of x is: 12
|
||||
```
|
||||
|
||||
This lets us modify `x`, but not deal with mutation. This is nice because we
|
||||
know that the compiler will let us know if we try to modify it later. Let’s
|
||||
assume that after we calculate `12`, we don’t want to modify `x` again. If we
|
||||
had written this program in a mutable style, like this:
|
||||
|
||||
```rust
|
||||
fn main() {
|
||||
let mut x = 5;
|
||||
x = x + 1;
|
||||
x = x * 2;
|
||||
|
||||
println!("The value of x is: {}", x);
|
||||
|
||||
x = 15;
|
||||
|
||||
println!("The value of x is: {}", x);
|
||||
}
|
||||
```
|
||||
|
||||
Rust is happy to let us mutate it again, to `15`. A similar program in our
|
||||
immutable style will let us know about that accidental mutation, however:
|
||||
|
||||
```rust,ignore
|
||||
fn main() {
|
||||
let x = 5;
|
||||
let x = x + 1;
|
||||
let x = x * 2;
|
||||
|
||||
println!("The value of x is: {}", x);
|
||||
|
||||
x = 15;
|
||||
|
||||
println!("The value of x is: {}", x);
|
||||
}
|
||||
```
|
||||
|
||||
If we try to compile, we get an error:
|
||||
|
||||
```bash
|
||||
$ cargo build
|
||||
Compiling bindings v0.1.0 (file:///projects/bindings)
|
||||
src/main.rs:8:5: 8:11 error: re-assignment of immutable variable `x` [E0384]
|
||||
src/main.rs:8 x = 15;
|
||||
^~~~~~
|
||||
src/main.rs:8:5: 8:11 help: run `rustc --explain E0384` to see a detailed explanation
|
||||
src/main.rs:4:9: 4:10 note: prior assignment occurs here
|
||||
src/main.rs:4 let x = x * 2;
|
||||
^
|
||||
error: aborting due to previous error
|
||||
Could not compile `bindings`.
|
||||
```
|
||||
|
||||
Exactly what we wanted.
|
||||
|
||||
Shadowing can take some time to get used to, but it’s very powerful, and works
|
||||
well with immutability.
|
||||
|
||||
There was one more thing we should talk about in the output from compiling our
|
||||
initial program. It’s this part:
|
||||
|
||||
```text
|
||||
src/main.rs:2:9: 2:10 warning: unused variable: `x`, #[warn(unused_variables)] on by default
|
||||
```
|
||||
|
||||
Here’s the two lines of relevant code:
|
||||
|
||||
```rust
|
||||
let x = 5;
|
||||
let x = 6;
|
||||
```
|
||||
|
||||
Rust knows that we shadowed `x`, but we never ended up using the initial value.
|
||||
This isn’t _wrong_, exactly, it just may not have been what we wanted. In this
|
||||
case, the compiler issues a ‘warning’, but still compiles our program. The
|
||||
`#[warn(unused_variables)]` syntax is called an ‘attribute’, which we’ll
|
||||
discuss in a later section. More specifically, a warning like this is called a
|
||||
‘lint’, which is an old term for the bits of sheep’s wool that you wouldn’t
|
||||
want to put in cloth. Similarly, this lint is telling us that we may have an
|
||||
extra bit of code we don’t need. Our program would work just fine without it.
|
||||
It’s worth listening to these warnings, and fixing the problems they point out.
|
||||
They can be signs of a larger problem. In this case, we may not have realized
|
||||
that we were shadowing `x`.
|
||||
|
||||
### Shadowing and scopes
|
||||
|
||||
Like any binding, a binding that shadows another binding will go away at the
|
||||
end of a scope. Here’s an example program:
|
||||
|
||||
```rust
|
||||
fn main() {
|
||||
let x = 5;
|
||||
|
||||
println!("Before shadowing, x is: {}", x);
|
||||
|
||||
{
|
||||
let x = 6;
|
||||
|
||||
println!("Now that x is shadowed, x is: {}", x);
|
||||
}
|
||||
|
||||
println!("After shadowing, x is: {}", x);
|
||||
}
|
||||
```
|
||||
|
||||
If we run this example, we can see the shadow appear and disappear:
|
||||
|
||||
```bash
|
||||
$ cargo run
|
||||
Compiling bindings v0.1.0 (file:///projects/bindings)
|
||||
Running `target/debug/bindings`
|
||||
Before shadowing, x is: 5
|
||||
Now that x is shadowed, x is: 6
|
||||
After shadowing, x is: 5
|
||||
```
|
572
src/ch03-03-variable-bindings-in-detail.md
Normal file
572
src/ch03-03-variable-bindings-in-detail.md
Normal file
@ -0,0 +1,572 @@
|
||||
## Variable Bindings in Detail
|
||||
|
||||
So far, we’ve created the simplest kind of variable binding, but the `let`
|
||||
statement has some more tricks up its sleeve. Now we'll look at doing more
|
||||
complex things: creating multiple bindings at once, adding type annotations,
|
||||
creating mutating bindings, understanding shadowing, and more.
|
||||
|
||||
### Creating Multiple Bindings
|
||||
|
||||
The previous example program just bound one variable, but it's also possible to
|
||||
create multiple variable bindings in one go. Let’s try a more complex example,
|
||||
creating two variable bindings at once. Change your example program to this:
|
||||
|
||||
<!-- Oh yeah, this is a much better place to have this example rather than the
|
||||
one I suggested above /Carol -->
|
||||
|
||||
```rust
|
||||
fn main() {
|
||||
let (x, y) = (5, 6);
|
||||
|
||||
println!("The value of x is: {}", x);
|
||||
println!("The value of y is: {}", y);
|
||||
}
|
||||
```
|
||||
|
||||
And enter `cargo run` to run it:
|
||||
|
||||
```bash
|
||||
$ cargo run
|
||||
Compiling bindings v0.1.0 (file:///projects/bindings)
|
||||
Running `target/debug/bindings`
|
||||
The value of x is: 5
|
||||
The value of y is: 6
|
||||
```
|
||||
|
||||
We’ve created two bindings with one `let` statement! The `let` statement binds
|
||||
the values in `(5, 6)` to the corresponding patterns of `(x, y)`. The first
|
||||
value `5` binds to the first part of the pattern, `x`, and the second value `6`
|
||||
binds to `y`. We could alternatively have used two `let` statements to the same
|
||||
effect, as follows:
|
||||
|
||||
```rust
|
||||
fn main() {
|
||||
let x = 5;
|
||||
let y = 6;
|
||||
}
|
||||
```
|
||||
|
||||
In simple cases like this, where we are only binding two variables, two `let`
|
||||
statements may be clearer in the code, but when you're creating many multiple
|
||||
bindings, it's useful to be able to do so all at once. Deciding which technique
|
||||
to use is mostly a judgement call, and as you become more proficient in Rust,
|
||||
you’ll be able to figure out which style is better in each case.
|
||||
|
||||
### Delayed Initialization
|
||||
|
||||
The examples so far have all provided bindings with an initial value, but that
|
||||
isn't always necessary. Rather, we can assign a value for the binding later,
|
||||
after the `let` statement. To try this out, write the following program:
|
||||
|
||||
```rust
|
||||
fn main() {
|
||||
let x;
|
||||
|
||||
x = 5;
|
||||
|
||||
println!("The value of x is: {}", x);
|
||||
}
|
||||
```
|
||||
|
||||
And enter `cargo run` to run it:
|
||||
|
||||
```bash
|
||||
$ cargo run
|
||||
Compiling bindings v0.1.0 (file:///projects/bindings)
|
||||
Running `target/debug/bindings`
|
||||
The value of x is: 5
|
||||
```
|
||||
|
||||
As you can see, this works just like the previous program, in which we assigned
|
||||
an initial value.
|
||||
|
||||
This raises an interesting question: what happens if we try to print out a
|
||||
binding before we declare a value? Let's find out. Modify your code to look
|
||||
like the following:
|
||||
|
||||
```rust,ignore
|
||||
fn main() {
|
||||
let x;
|
||||
|
||||
println!("The value of x is: {}", x);
|
||||
|
||||
x = 5;
|
||||
}
|
||||
```
|
||||
|
||||
When you enter `cargo run` to run this code, you should see output like this
|
||||
after the command:
|
||||
|
||||
```bash
|
||||
Compiling bindings v0.1.0 (file:///projects/bindings)
|
||||
src/main.rs:4:39: 4:40 error: use of possibly uninitialized variable: `x` [E0381]
|
||||
src/main.rs:4 println!("The value of x is: {}", x);
|
||||
^
|
||||
<std macros>:2:25: 2:56 note: in this expansion of format_args!
|
||||
<std macros>:3:1: 3:54 note: in this expansion of print! (defined in <std macros>)
|
||||
src/main.rs:4:5: 4:42 note: in this expansion of println! (defined in <std macros>)
|
||||
src/main.rs:4:39: 4:40 help: run `rustc --explain E0381` to see a detailed explanation
|
||||
error: aborting due to previous error
|
||||
Could not compile `bindings`.
|
||||
|
||||
To learn more, run the command again with --verbose.
|
||||
```
|
||||
|
||||
There's been an error! The compiler won’t let us write a program like this, and
|
||||
instead it requests that you assign a value to the variable `x`. This is our
|
||||
first example of the compiler helping us find an error in our program.
|
||||
Different programming languages have different ways of approaching this
|
||||
problem. Some languages will always initialize values with some sort of
|
||||
default. Other languages leave the value uninitialized, and make no promises
|
||||
about what happens if you try to use something before initialization. Rust
|
||||
responds with an error to prod the programmer to declare the value they want.
|
||||
We must initialize any variable before we can use it.
|
||||
|
||||
PROD: START BOX
|
||||
######Extended Error Explanations
|
||||
|
||||
Now that you've seen an example of a Rust error, I want to point out one
|
||||
particularly useful aspect of errors. Rust encourages you to seek further
|
||||
information on the kind of error you've received with output like this:
|
||||
|
||||
```bash
|
||||
src/main.rs:4:39: 4:40 help: run `rustc --explain E0381` to see a detailed explanation
|
||||
```
|
||||
|
||||
This tells us that if we pass the `--explain` flag to `rustc` with the provided
|
||||
error code, we can see an extended explanation, which will try to explain
|
||||
common causes of and solutions to that kind of error. Not every error has a
|
||||
longer explanation, but many do. Here’s the explanation for the `E0381` error
|
||||
we received previously:
|
||||
|
||||
```bash
|
||||
$ rustc --explain E0381
|
||||
It is not allowed to use or capture an uninitialized variable. For example:
|
||||
|
||||
fn main() {
|
||||
let x: i32;
|
||||
|
||||
let y = x; // error, use of possibly uninitialized variable
|
||||
|
||||
To fix this, ensure that any declared variables are initialized before being
|
||||
used.
|
||||
```
|
||||
|
||||
These explanations can really help if you’re stuck on an error, so don't
|
||||
hesitate to look up the error code. The compiler is your friend, and it's there
|
||||
to help.
|
||||
|
||||
PROD: END BOX
|
||||
|
||||
### Mutable bindings
|
||||
|
||||
By default, variable bindings are *immutable*, meaning that once a value is
|
||||
bound, you can't change that value. Try writing the following sample program to
|
||||
illustrate this:
|
||||
|
||||
```rust,ignore
|
||||
fn main() {
|
||||
let x = 5;
|
||||
|
||||
x = 6;
|
||||
|
||||
println!("The value of x is: {}", x);
|
||||
}
|
||||
```
|
||||
|
||||
Save and run the program, and you should receive another error message, as in
|
||||
this output:
|
||||
|
||||
```bash
|
||||
$ cargo run
|
||||
Compiling bindings v0.1.0 (file:///projects/bindings)
|
||||
src/main.rs:4:5: 4:10 error: re-assignment of immutable variable `x` [E0384]
|
||||
src/main.rs:4 x = 6;
|
||||
^~~~~
|
||||
src/main.rs:4:5: 4:10 help: run `rustc --explain E0384` to see a detailed explanation
|
||||
src/main.rs:2:9: 2:10 note: prior assignment occurs here
|
||||
src/main.rs:2 let x = 5;
|
||||
^
|
||||
```
|
||||
|
||||
The error includes the message `re-assigment of immutable variable` because the
|
||||
program tried to assign a second value to the `x` variable. But bindings are
|
||||
immutable only by default; you can make them mutable by adding `mut` in front
|
||||
of the variable name. For example, change the program you just wrote to the
|
||||
following:
|
||||
|
||||
```rust
|
||||
fn main() {
|
||||
let mut x = 5;
|
||||
|
||||
println!("The value of x is: {}", x);
|
||||
|
||||
x = 6;
|
||||
|
||||
println!("The value of x is: {}", x);
|
||||
}
|
||||
```
|
||||
|
||||
Running this, we get:
|
||||
|
||||
```bash
|
||||
$ cargo run
|
||||
Compiling bindings v0.1.0 (file:///projects/bindings)
|
||||
Running `target/debug/bindings`
|
||||
The value of x is: 5
|
||||
The value of x is: 6
|
||||
```
|
||||
|
||||
Using `mut`, we change the value that `x` binds to from `5` to `6`. Note,
|
||||
however, that `mut` is part of the pattern in the `let` statement. This becomes
|
||||
more obvious if we try to add mutability to a pattern that binds multiple
|
||||
variables in the same way as we did for a single variable, like this:
|
||||
<!--- Steve: regarding our previous note on avoiding negative examples -- if we
|
||||
do want to show a negative example, we ought to make it clear that that's
|
||||
what's going to happen, so the reader doesn't go through expecting their code
|
||||
to work and then gets a faceful of error. I've edited above slightly with this
|
||||
in mind /Liz -->
|
||||
|
||||
```rust,ignore
|
||||
fn main() {
|
||||
let (mut x, y) = (5, 6);
|
||||
|
||||
x = 7;
|
||||
y = 8;
|
||||
}
|
||||
```
|
||||
|
||||
If you run this code, the compiler will output an error:
|
||||
|
||||
```bash
|
||||
$ cargo run
|
||||
Compiling bindings v0.1.0 (file:///projects/bindings)
|
||||
src/main.rs:5:5: 5:10 error: re-assignment of immutable variable `y` [E0384]
|
||||
src/main.rs:5 y = 8;
|
||||
^~~~~
|
||||
src/main.rs:5:5: 5:10 help: run `rustc --explain E0384` to see a detailed explanation
|
||||
src/main.rs:2:17: 2:18 note: prior assignment occurs here
|
||||
src/main.rs:2 let (mut x, y) = (5, 6);
|
||||
^
|
||||
```
|
||||
|
||||
The way `mut` is used here, the compiler is fine with reassigning the `x`
|
||||
variable, but not the `y` variable. That's because `mut` only applies to the
|
||||
name that directly follows it, not the whole pattern. For the compiler to allow
|
||||
you to reassign the `y` variable, you'd need to write the pattern as `(mut x,
|
||||
mut y)` instead.
|
||||
|
||||
<!-- If I change the example to have `(mut x, mut y)`, it compiles but gives me
|
||||
warnings about unused variables and assignments. Do we care about that? Could
|
||||
be easily solved by throwing in some println!s as in previous examples. /Carol
|
||||
-->
|
||||
|
||||
One thing to know about mutating bindings: `mut` allows you to mutate _the
|
||||
binding_, but not _what the name binds to_. In other words, the value is not
|
||||
what changes, but rather the path between the value and the name. For example:
|
||||
|
||||
```rust
|
||||
fn main() {
|
||||
let mut x = 5;
|
||||
|
||||
x = 6;
|
||||
}
|
||||
```
|
||||
|
||||
This does not change the value that `x` is bound to, but creates a new value
|
||||
(`6`) and changes the binding so that it binds the name `x` to this new value
|
||||
instead. This subtle but important difference will become more important as
|
||||
your Rust programs get more complex.
|
||||
|
||||
<!--
|
||||
I feel like this example doesn't make the concept of "mutating the binding, not
|
||||
the value" clear to me... what about this instead?
|
||||
|
||||
```rust
|
||||
fn main() {
|
||||
let mut x = 5;
|
||||
let y = x;
|
||||
|
||||
x = x + 1;
|
||||
|
||||
println!("The value of x is {}. The value of y is {}.", x, y);
|
||||
}
|
||||
```
|
||||
|
||||
which prints out "The value of x is 6. The value of y is 5.". The 5 is still
|
||||
around in y, where in the previous example I don't really have a way to *see*
|
||||
that. This example also has some subtlety around value vs reference that I'm
|
||||
not sure *I* understand precisely correctly, so perhaps this isn't really a
|
||||
good example of "mutating the binding, not the value" ;)
|
||||
|
||||
I do feel like either this example could be more illustrative or this whole
|
||||
aside should be taken out to be addressed later, though.
|
||||
/Carol
|
||||
-->
|
||||
|
||||
### Variable Binding Scope
|
||||
|
||||
Another important thing to know about variable bindings is that they are only
|
||||
valid as long as they are *in scope*. That scope begins at the point where the
|
||||
binding is declared, and ends with the curly brace that closes the block of
|
||||
code containing that binding. We cannot access bindings "before they come into
|
||||
scope" or "after they go out of scope." Here’s an example to illustrate this:
|
||||
|
||||
<!-- I feel like these examples don't really illustrate anything since the
|
||||
println!s don't *use* x or y. They're basically comments. What do you think
|
||||
about encouraging the reader to modify the println!s in order to use x and y,
|
||||
to see when the compiler errors or not?
|
||||
|
||||
For example, the first println! in the next example that says `println!("x is
|
||||
not yet in scope");` could instead say `println!("x is not yet in scope. Try to
|
||||
print x in this statement to see the compiler error!");`. When x is in scope,
|
||||
the println!s could use it.
|
||||
|
||||
/Carol -->
|
||||
|
||||
```rust
|
||||
fn main() {
|
||||
println!("x is not yet in scope");
|
||||
|
||||
let x = 5;
|
||||
|
||||
println!("x is now in scope");
|
||||
|
||||
println!("In real code, we’d now do a bunch of work.");
|
||||
|
||||
println!("x will go out of scope now! The next curly brace is ending the main function.");
|
||||
}
|
||||
```
|
||||
|
||||
The variable binding for `x` goes out of scope with the last curly brace in the
|
||||
`main()` function.
|
||||
|
||||
This example only has one scope, though. In Rust, it's possible to create
|
||||
arbitrary scopes within a scope by placing code within another pair of curly
|
||||
braces (we'll look at this more in the next chapter). For example:
|
||||
|
||||
```rust
|
||||
fn main() {
|
||||
|
||||
println!("x is not yet in scope");
|
||||
|
||||
let x = 5;
|
||||
|
||||
println!("x is now in scope");
|
||||
|
||||
println!("Let’s start a new scope!");
|
||||
|
||||
{
|
||||
let y = 5;
|
||||
|
||||
println!("y is now in scope");
|
||||
println!("x is also still in scope");
|
||||
|
||||
println!("y will go out of scope now!");
|
||||
println!("The next curly brace is ending the scope we started.");
|
||||
}
|
||||
|
||||
println!("x is still in scope, but y is now out of scope and is not usable");
|
||||
|
||||
println!("x will go out of scope now! The next curly brace is ending the main function.");
|
||||
}
|
||||
```
|
||||
|
||||
The `y` variable is only in scope in the section of the code that's between the
|
||||
nested pair of curly braces, whereas `x` is in scope from the `let` statement
|
||||
that binds it until the final curly brace. The scope of bindings will become
|
||||
much more important later as you learn about references in Chapter XX.
|
||||
|
||||
### Shadowing Earlier Bindings
|
||||
|
||||
<!--- TR: Can you help add a definition of shadowing, to let the reader know
|
||||
what to look out for here? The shadowed value takes precedance over the
|
||||
original value? /Liz -->
|
||||
<!-- I wanted to make sure I got this correct, so I
|
||||
did some research-- the wikipedia page on variable shadowing
|
||||
(https://en.wikipedia.org/wiki/Variable_shadowing) says "This outer variable is
|
||||
said to be shadowed by the inner variable, while the inner identifier is said
|
||||
to mask the outer identifier." so I reworded the sentence needing the
|
||||
definition since it was backwards. /Carol -->
|
||||
One final thing about bindings: they can *shadow* previous bindings with the
|
||||
same name. Shadowing is what happens when you declare two bindings with the
|
||||
same name. We say that the first binding is ‘shadowed’ by the second, which
|
||||
means that the second binding's value is what you will see when you use the
|
||||
variable after the second binding. This can be useful if you’d like to perform
|
||||
a few transformations on a value, but still leave the binding immutable. For
|
||||
example:
|
||||
|
||||
```rust
|
||||
fn main() {
|
||||
let x = 5;
|
||||
|
||||
let x = x + 1;
|
||||
|
||||
let x = x * 2;
|
||||
|
||||
println!("The value of x is: {}", x);
|
||||
}
|
||||
```
|
||||
|
||||
This program first binds `x` to a value of `5`. Then, it shadows `x`, taking
|
||||
the original value and adding `1` so that the value of `x` is then `6`. The
|
||||
third `let` statement shadows `x` again, taking the previous value and
|
||||
multiplying it by `2` to give `x` a final value of `12`. If you run this, it
|
||||
will output:
|
||||
|
||||
```bash
|
||||
$ cargo run
|
||||
Compiling bindings v0.1.0 (file:///projects/bindings)
|
||||
Running `target/debug/bindings`
|
||||
The value of x is: 12
|
||||
```
|
||||
|
||||
Shadowing is useful because it lets us modify `x` without having to make the
|
||||
variable mutable. This means the compiler will still warn us if we accidentally
|
||||
try to mutate `x` directly later. For example, say after calculating `12` we
|
||||
don’t want `x` to be modified again; if we write the program in a mutable
|
||||
style, like this:
|
||||
|
||||
```rust
|
||||
fn main() {
|
||||
let mut x = 5;
|
||||
|
||||
x = x + 1;
|
||||
x = x * 2;
|
||||
|
||||
println!("The value of x is: {}", x);
|
||||
|
||||
x = 15;
|
||||
|
||||
println!("The value of x is: {}", x);
|
||||
}
|
||||
```
|
||||
|
||||
Rust is happy to let us mutate `x` again, to `15`. A similar program using the
|
||||
default immutable style, however, will let us know about that accidental
|
||||
mutation. Here's an example:
|
||||
|
||||
```rust,ignore
|
||||
fn main() {
|
||||
let x = 5;
|
||||
let x = x + 1;
|
||||
let x = x * 2;
|
||||
|
||||
println!("The value of x is: {}", x);
|
||||
|
||||
x = 15;
|
||||
|
||||
println!("The value of x is: {}", x);
|
||||
}
|
||||
```
|
||||
|
||||
If we try to compile this, we get an error:
|
||||
|
||||
```bash
|
||||
$ cargo run
|
||||
Compiling bindings v0.1.0 (file:///projects/bindings)
|
||||
src/main.rs:8:5: 8:11 error: re-assignment of immutable variable `x` [E0384]
|
||||
src/main.rs:8 x = 15;
|
||||
^~~~~~
|
||||
src/main.rs:8:5: 8:11 help: run `rustc --explain E0384` to see a detailed explanation
|
||||
src/main.rs:4:9: 4:10 note: prior assignment occurs here
|
||||
src/main.rs:4 let x = x * 2;
|
||||
^
|
||||
error: aborting due to previous error
|
||||
Could not compile `bindings`.
|
||||
```
|
||||
|
||||
Since we don't want the binding to be mutable, this exactly what should happen.
|
||||
|
||||
#### Shadowing Over Bindings
|
||||
|
||||
You can also shadow bindings over one another, without re-using the initial
|
||||
binding in the value. Here's how that looks:
|
||||
|
||||
```rust
|
||||
fn main() {
|
||||
let x = 5;
|
||||
let x = 6;
|
||||
|
||||
println!("The value of x is: {}", x);
|
||||
}
|
||||
```
|
||||
|
||||
Running this sample program, we can see the shadowing in action:
|
||||
|
||||
```bash
|
||||
$ cargo run
|
||||
Compiling bindings v0.1.0 (file:///projects/bindings)
|
||||
src/main.rs:2:9: 2:10 warning: unused variable: `x`, #[warn(unused_variables)] on by default
|
||||
src/main.rs:2 let x = 5;
|
||||
^
|
||||
Running `target/debug/bindings`
|
||||
The value of x is: 6
|
||||
```
|
||||
|
||||
Rust gives the value of `x` as `6`, which is the value from the *second* `let`
|
||||
statement. There are a few interesting things in this output. First, that Rust
|
||||
will compile and run the program without issue. This is because we haven't
|
||||
mutated the value; instead, we declared a _new_ binding that is _also_ named
|
||||
`x`, and gave it a new value.
|
||||
|
||||
The other interesting thing in this output is this error line:
|
||||
|
||||
```bash
|
||||
src/main.rs:2:9: 2:10 warning: unused variable: `x`, #[warn(unused_variables)] on by default
|
||||
```
|
||||
|
||||
Rust is pointing out that we shadowed `x`, but never used the initial value.
|
||||
Doing so isn’t _wrong_, but Rust is checking whether this is intentional and
|
||||
not just a mistake. In this case, the compiler issues a warning, but still
|
||||
compiles our program. A warning like this is called a *lint*, which is an old
|
||||
term for the bits of fluff and fibers in sheep’s wool that you wouldn't want to
|
||||
put in cloth.
|
||||
|
||||
Similarly, this lint is telling us that we may have an extra bit of code (the
|
||||
statement `let x = 5`) that we don’t need. Even though our program works just
|
||||
fine, listening to these warnings and fixing the problems they point out is
|
||||
worthwhile, as they can be signs of a larger problem. In this case, we may not
|
||||
have realized that we were shadowing `x`, when we meant to, say, define a new
|
||||
variable with a different name.
|
||||
|
||||
Shadowing can take some time to get used to, but it’s very powerful and works
|
||||
well with immutability.
|
||||
|
||||
#### Shadowing and Scopes
|
||||
|
||||
Like any binding, a binding that shadows another binding becomes invalid at the
|
||||
end of a scope. Here’s an example program to illustrate this:
|
||||
|
||||
```rust
|
||||
fn main() {
|
||||
let x = 5;
|
||||
|
||||
println!("Before shadowing, x is: {}", x);
|
||||
|
||||
{
|
||||
let x = 6;
|
||||
|
||||
println!("Now that x is shadowed, x is: {}", x);
|
||||
}
|
||||
|
||||
println!("After shadowing, x is: {}", x);
|
||||
}
|
||||
```
|
||||
|
||||
This code first creates the `x` variable and prints `x` to the terminal. Then,
|
||||
inside a new scope, it creates a new binding for `x` with a new value, and
|
||||
prints that value. When the arbitrary scope ends, `x` is printed once more. If
|
||||
we run this example, we can see the shadow appear and disappear in the output:
|
||||
|
||||
```bash
|
||||
$ cargo run
|
||||
Compiling bindings v0.1.0 (file:///projects/bindings)
|
||||
Running `target/debug/bindings`
|
||||
Before shadowing, x is: 5
|
||||
Now that x is shadowed, x is: 6
|
||||
After shadowing, x is: 5
|
||||
```
|
||||
|
||||
In this case, the binding value reverts to the original value once the shadow
|
||||
binding goes out of scope.
|
Loading…
Reference in New Issue
Block a user