rust-book-cn/src/variable-bindings.md
2016-02-16 22:43:13 -07:00

17 KiB
Raw Blame History

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:

$ 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:

fn main() {
    println!("Hello, world!");
}

Lets replace that program with this one:

fn main() {
    let x = 5;

    println!("The value of x is: {}", x);
}

And finally, run it:

$ 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. Lets break this down, line by line.

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.

    let x = 5;

This is our first variable binding, which we create with a let statement.

This let statement has this form:

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:

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, well see more complex and powerful patterns as we go along.

Before we do that, though, lets finish investigating this example. Heres the next line:

    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. The format string can contain the special text {}. Each instance of {} corresponds to an additional argument. Heres an example:

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 well discuss later.

}

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:

The value of x is: 5

We assign 5 to a binding, x, and then print it to the screen with println!.

Multiple binding

Lets try a more complex pattern. Change our example program to this:

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:

$ 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

Weve created two bindings with one let! Heres our pattern:

(x, y)

And heres the value:

(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:

fn main() {
    let x = 5;
    let y = 6;
}

In simple cases like this, two lets 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.

Type annotations

You may have noticed that we didnt 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. Lets look at the example again:

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. Well talk more about Rusts basic types in section 3.3.

Heres what a let statement with a type annotation looks like:

fn main() {
    let x: i32 = 5;
}

We can add a colon, followed by the type name. Heres the structure of a let statement with a type annotation:

let PATTERN: TYPE = VALUE;

Note that the colon and the TYPE go after the PATTERN, not in the pattern itself. As an example, heres our more complex pattern with two bindings:

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:

fn main() {
    let x;

    x = 5;

    println!("The value of x is: {}", x);
}

And run it with cargo run:

$ cargo run
   Compiling bindings v0.1.0 (file:///projects/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:

fn main() {
    let x;

    println!("The value of x is: {}", x);

    x = 5;
}

We can find out the answer with cargo run:

   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 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:

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:

$ 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:

fn main() {
    let x = 5;

    x = 6;

    println!("The value of x is: {}", x);
}

cargo run has the answer for us:

$ 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. 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:

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:

$ 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; its using mut in a pattern. This becomes more obvious with our () pattern:

fn main() {
    let (mut x, y) = (5, 6);

    x = 7;
    y = 8;
}

The compiler will complain about this program:

$ 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);
                              ^

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:

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. 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.

Scope

Variable bindings have a scope in which theyre 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. Heres an example:

fn main() {
    println!("x is not yet in scope");

    let x = 5;
    println!("x is now in scope");

    println!("In real code, wed 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 }:

fn main() {
    println!("x is not yet in scope");

    let x = 5;
    println!("x is now in scope");

    println!("Lets 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. Heres a sample program:

fn main() {
    let x = 5;
    let x = 6;

    println!("The value of x is: {}", x);
}

Running it, we can see the shadowing in action:

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:

fn main() {
    let x = 5;
    let x = x + 1;
    let x = x * 2;

    println!("The value of x is: {}", x);
}

This will print:

   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. 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:

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:

$ 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 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:

src/main.rs:2:9: 2:10 warning: unused variable: `x`, #[warn(unused_variables)] on by default

Heres the two lines of relevant code:

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.

Shadowing and scopes

Like any binding, a binding that shadows another binding will go away at the end of a scope. Heres an example program:

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:

$ 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