2.1 Variable bindings

Most languages have variables in them in order to store values, but Rust has something that’s slightly more powerful known as variable bindings. Whilst there isn’t a huge difference between variables—of other language—and variable bindings in Rust, the name gets a whole lot clearer after working with them for a while.

Let’s go ahead and define a new variable binding straight off the bat:

let x = 3;

This defines a variable binding that binds x to the value of three. The keyword let is what binds a value to a number.

It is worth no notice before carrying on that functions are—in the same way as Cargo projects—by convention written using lowercase snake case.

Type annotations

Rust is a statically typed language—this would usually mean that one would have to specify the type of every variable. Had that been the case, the aforementioned example wouldn’t have compiled, which as a matter of fact it does. Luckily Rust has something called type inference, which means that it can figure out what type of variable we’re looking to create and automatically makes it that type upon compilation.

There are some cases where you might want to specify the type of your Rust variables—these are the times you make use of Rust’s type annotations:

let x: i32 = 3;

This code says that the name x, which has the type i32, is bound to the value of three. This thus explicitly changes the type of x to an i32—being an integer with the ability to store 32 bits of data. A : is placed after the name to clarify that it’s the binding by the name of x we wish to make into an i32.

Primitive types

The i32 is one of many primitive types in Rust. Primitive types are all different types that define what type of data a certain binding may be used to store.

There are many primitive types contained by the Rust standard library. Feel free to go over them by yourself over at the Rust documentation. These are covered in-depth in the section Primitive types.

Mutability

By default, Rust variable bindings are immutable—meaning that it’s impossible to change their values. The reason for this is due to the safety Rust is attempting to achieve in its design. The less the user can do, the less the user can severely mess things up. This doesn’t mean the user can’t create mutable bindings, but the less mutable bindings there are, the less prone to error the program will be.

Have a look at this code:

let x = 4;

x = 5;

This will generate a rather depressing error:

error[E0384]: re-assignment of immutable variable ’x’
--src\main.rs:4:6
  |
3 |     let x = 2;
  |         - first assignment to ’x’
4 |      x = 3;
  |      ^^^^^ re-assignment of immutable variable

The reason as to why this happens is because the binding whose value we’re attempting to change is immutable—being set to mutable by default. In order for us to be able to change it, we first need to tell Rust that it’s a mutable binding. For this, we use the mut keyword:

let mut x = 4;

x = 5;

Try your best to keep mutable bindings at a bare minimum—use immutable variable bindings whenever you can.

Overwrite

You may at any time overwrite the value connected with a name and bind the name to a new value.

let x = 4;
let x = 5;

Due to us defining a new variable binding going by the same name as another, we won’t be able to access the value bound to the old one anymore; there's no way in which we can access the old x anymore.

Patterns

You can use patterns—which are covered in-depth in the section Pattens—whose job is to give us an easier time doing advanced things. For example, we can use patterns in order to bind multiple names to multiple values at the same time inside a let:

let (x, y) = (1, 2);

This means that x now is bound to the value of one and y is bound to the value of two.

The reason this works is because what comes after let is not actually a binding name, but rather a pattern—in its true nature, what we’re doing is known as ‘destructuring’. The right-hand side of the assignment is a tuple—which will just like patterns—be discussed at a later time: over in the section Primitive types.

Exercises

  • Create a variable binding and bind it to any value.
  • Define a mutable variable bound to any value—change its value.
  • Make a variable binding that explicistly is of type i32 and set it to a really big value. The Rust compiler will be proud of you for being so clear in what it is you want.

Moreover

If you have not already, you’ll soon come to realize that variable bindings are really powerful—as soon as you start learning about patterns, you’ll really understand why variable bindings are pure amazing!