First draft of variable bindings

This commit is contained in:
Steve Klabnik 2015-12-16 17:39:12 -05:00
parent 524a87008d
commit 5132d63221

View File

@ -1,2 +1,485 @@
# Variable Bindings
The foundation of virtually every program is the ability to store and modify data.
Rust programs are no different.
Lets start with a short example.
## The basics of bindings
First, well generate a new project with Cargo.
Open a terminal, and navigate to the directory where youd like to keep your projects.
From there, lets 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!");
}
```
Lets 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:///home/steve/tmp/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.
Lets break this down, line by line.
```rust,ignore
fn main() {
```
The `main()` function is the entry point of every Rust program.
Well 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 functions body.
```rust,ignore
let x = 5;
```
This is our first variable binding.
This looks like many languages, but there are some subtleties.
We create variable bindings with a `let` statement, which looks like this:
```text
let PATTERN = VALUE;
```
In our example, the `PATTERN` is a humble `x`, and the `VALUE` is a simple `5`.
The `let` statement takes the `VALUE`, and matches it to the `PATTERN`, creating new bindings.
Therefore, our example creates a new binding, `x`, and gives it an initial value of `5`.
Patterns are a big part of Rust, we will see them in many places.
That seems like a lot of explanation for a basic assignment statement!
The trick is that these patterns can become more complex, and have a lot of power.
Well explore more complex patterns as we learn more.
Before we do that, though, lets finish investigating this example.
Heres the next line:
```rust,ignore
println!("The value of x is: {}", x);
```
The `println!` macro prints text to the screen.
We can tell that its a macro due to the `!`.
We wont learn how to write macros until much later in the book, but well 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.
This is why its a macro; Rust does not support optional arguments for regular functions.
The format string can contain the special text `{}`.
If it does, it fills in that place in the string with the values of the other arguments.
In this case, weve passed `x`.
You can think of `{}` as little crab pincers, holding the value in place.
This placeholder has a number of more advanced formatting options that well 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 assignment
Lets 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:///home/steve/tmp/bindings)
Running `target/debug/bindings`
The value of x is: 5
The value of y is: 6
```
Weve created two bindings with one `let`!
Heres our pattern:
```text
(x, y)
```
And heres 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
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, well figure out which style is better, but its mostly a judgement call.
## 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:///home/steve/tmp/bindings)
Running `target/debug/bindings`
The value of x is: 5
```
Its all good.
This raises a question, though: what if we try to print out a binding before we declare a value?
Heres 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:///home/steve/tmp/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 wont 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
Theres 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.
Heres `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 youre stuck on an error.
The compiler is your friend, and is here to help.
## Mutable bindings
What about changing the value of a binding? Heres 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:///home/steve/tmp/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`.
Thats right: bindings are immutable.
But theyre only immutable by default.
In a pattern, when were creating a new name, we can add `mut` in front to make the binding a mutable one.
Heres 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:///home/steve/tmp/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; its 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:///home/steve/tmp/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);
^
```
Its 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 havent covered yet: `mut` allows you to mutate _the binding_, but not _what the binding binds to_.
In other words:
```rust
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.
Its 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.
Well talk about that in the next section, when we discuss functions.
## Shadowing
A final thing about bindings: they can shadow previous bindings with the same name.
Heres 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 didnt 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 youd 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:///home/steve/tmp/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.
Lets assume that after we calculate `12`, we dont want to modify `x` again.
If we had written this program in a mutable style, like this:
```
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:///home/steve/tmp/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 its very powerful, and works well with immutability.
There was one more thing we should talk about in the output from compiling our initial program.
Its this part:
```text
src/main.rs:2:9: 2:10 warning: unused variable: `x`, #[warn(unused_variables)] on by default
```
Heres 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 isnt _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 well discuss in a later section.
More specifically, a warning like this is called a lint, which is an old term for the bits of sheeps wool that you wouldnt want to put in cloth.
Similarly, this lint is telling us that we may have an extra bit of code we dont need.
Our program would work just fine without it.
Its 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`.