Welcome! This book will teach you about the Rust Programming Language. Rust is a systems programming language focused on three goals: safety, speed, and concurrency. It maintains these goals without having a garbage collector, making it a useful language for a number of use cases other languages aren’t good at: embedding in other languages, programs with specific space and time requirements, and writing low-level code, like device drivers and operating systems. It improves on current languages targeting this space by having a number of compile-time safety checks that produce no runtime overhead, while eliminating all data races. Rust also aims to achieve ‘zero-cost abstractions’ even though some of these abstractions feel like those of a high-level language. Even then, Rust still allows precise control like a low-level language would.
“The Rust Programming Language” is split into seven sections. This introduction is the first. After this:
After reading this introduction, you’ll want to dive into either ‘Learn Rust’ or ‘Syntax and Semantics’, depending on your preference: ‘Learn Rust’ if you want to dive in with a project, or ‘Syntax and Semantics’ if you prefer to start small, and learn a single concept thoroughly before moving onto the next. Copious cross-linking connects these parts together.
The source files from which this book is generated can be found on Github: github.com/rust-lang/rust/tree/master/src/doc/trpl
Is Rust a language you might be interested in? Let’s examine a few small code samples to show off a few of its strengths.
The main concept that makes Rust unique is called ‘ownership’. Consider this small example:
fn main() { let mut x = vec!["Hello", "world"]; }fn main() { let mut x = vec!["Hello", "world"]; }
This program makes a variable binding named x
`x. The value of this binding is a
`. The value of this
binding is a Vec<T>
`Vec, a ‘vector’, that we create through a [macro][macro] defined in the standard library. This macro is called
`, a ‘vector’, that we create through a macro
defined in the standard library. This macro is called vec
`vec, and we invoke macros with a
`, and we invoke
macros with a !
`!. This follows a general principle of Rust: make things explicit. Macros can do significantly more complicated things than function calls, and so they’re visually distinct. The
`. This follows a general principle of Rust: make things
explicit. Macros can do significantly more complicated things than function
calls, and so they’re visually distinct. The !
`!` also helps with parsing,
making tooling easier to write, which is also important.
We used mut
`mutto make
` to make x
`x` mutable: bindings are immutable by default in Rust.
We’ll be mutating this vector later in the example.
It’s also worth noting that we didn’t need a type annotation here: while Rust is statically typed, we didn’t need to explicitly annotate the type. Rust has type inference to balance out the power of static typing with the verbosity of annotating types.
Rust prefers stack allocation to heap allocation: x
`xis placed directly on the stack. However, the
` is placed directly on the
stack. However, the Vec<T>
`Vec
Earlier, we mentioned that ‘ownership’ is the key new concept in Rust. In Rust
parlance, x
`xis said to ‘own’ the vector. This means that when
` is said to ‘own’ the vector. This means that when x
`xgoes out of scope, the vector’s memory will be de-allocated. This is done deterministically by the Rust compiler, rather than through a mechanism such as a garbage collector. In other words, in Rust, you don’t call functions like
` goes out of
scope, the vector’s memory will be de-allocated. This is done deterministically
by the Rust compiler, rather than through a mechanism such as a garbage
collector. In other words, in Rust, you don’t call functions like malloc
`mallocand
` and
free
`free` yourself: the compiler statically determines when you need to allocate
or deallocate memory, and inserts those calls itself. To err is to be human,
but compilers never forget.
Let’s add another line to our example:
fn main() { let mut x = vec!["Hello", "world"]; let y = &x[0]; }fn main() { let mut x = vec!["Hello", "world"]; let y = &x[0]; }
We’ve introduced another binding, y
`y. In this case,
`. In this case, y
`y` is a ‘reference’ to
the first element of the vector. Rust’s references are similar to pointers in
other languages, but with additional compile-time safety checks. References
interact with the ownership system by ‘borrowing’ what they point
to, rather than owning it. The difference is, when the reference goes out of
scope, it will not deallocate the underlying memory. If it did, we’d
de-allocate twice, which is bad!
Let’s add a third line. It looks innocent enough, but causes a compiler error:
fn main() { let mut x = vec!["Hello", "world"]; let y = &x[0]; x.push("foo"); }fn main() { let mut x = vec!["Hello", "world"]; let y = &x[0]; x.push("foo"); }
push
`push` is a method on vectors that appends another element to the end of the
vector. When we try to compile this program, we get an error:
error: cannot borrow `x` as mutable because it is also borrowed as immutable
x.push("foo");
^
note: previous borrow of `x` occurs here; the immutable borrow prevents
subsequent moves or mutable borrows of `x` until the borrow ends
let y = &x[0];
^
note: previous borrow ends here
fn main() {
}
^
Whew! The Rust compiler gives quite detailed errors at times, and this is one
of those times. As the error explains, while we made our binding mutable, we
still cannot call push
`push. This is because we already have a reference to an element of the vector,
`. This is because we already have a reference to an
element of the vector, y
`y. Mutating something while another reference exists is dangerous, because we may invalidate the reference. In this specific case, when we create the vector, we may have only allocated space for three elements. Adding a fourth would mean allocating a new chunk of memory for all those elements, copying the old values over, and updating the internal pointer to that memory. That all works just fine. The problem is that
`. Mutating something while another reference exists
is dangerous, because we may invalidate the reference. In this specific case,
when we create the vector, we may have only allocated space for three elements.
Adding a fourth would mean allocating a new chunk of memory for all those elements,
copying the old values over, and updating the internal pointer to that memory.
That all works just fine. The problem is that y
`ywouldn’t get updated, and so we’d have a ‘dangling pointer’. That’s bad. Any use of
` wouldn’t get updated, and so
we’d have a ‘dangling pointer’. That’s bad. Any use of y
`y` would be an error in
this case, and so the compiler has caught this for us.
So how do we solve this problem? There are two approaches we can take. The first is making a copy rather than using a reference:
fn main() { let mut x = vec!["Hello", "world"]; let y = x[0].clone(); x.push("foo"); }fn main() { let mut x = vec!["Hello", "world"]; let y = x[0].clone(); x.push("foo"); }
Rust has move semantics by default, so if we want to make a copy of some
data, we call the clone()
`clone()method. In this example,
` method. In this example, y
`yis no longer a reference to the vector stored in
` is no longer a reference
to the vector stored in x
`x, but a copy of its first element,
`, but a copy of its first element, "Hello"
`"Hello". Now that we don’t have a reference, our
`. Now
that we don’t have a reference, our push()
`push()` works just fine.
If we truly want a reference, we need the other option: ensure that our reference goes out of scope before we try to do the mutation. That looks like this:
fn main() { let mut x = vec!["Hello", "world"]; { let y = &x[0]; } x.push("foo"); }fn main() { let mut x = vec!["Hello", "world"]; { let y = &x[0]; } x.push("foo"); }
We created an inner scope with an additional set of curly braces. y
`ywill go out of scope before we call
` will go out of
scope before we call push()
`push()`, and so we’re all good.
This concept of ownership isn’t just good for preventing dangling pointers, but an entire set of related problems, like iterator invalidation, concurrency, and more.