Rust Programming Language - A Comprehensive Beginner - S Guide To Rust
Rust Programming Language - A Comprehensive Beginner - S Guide To Rust
3nd edition
2021
By Claudia Alves
"Programming isn't about what you know; it's about what you can
figure out.” - Chris Pine
.
Memlnc.com
COMPARING RIDDLES.............................................................................................................51
ITERATION...............................................................................................................................58
COMPLETED! ..........................................................................................................................68
RUST WITHIN OTHER LANGUAGES..........................................................................................90
THE PROBLEM.........................................................................................................................91
A RUST LIBRARY......................................................................................................................93
RUBY........................................................................................................................................97
PYTHON...................................................................................................................................99
NODE.JS.................................................................................................................................100
$ NPM INSTALL FFI................................................................................................................100
CHAPTER III______________________________________________________________102
RUST CASH..........................................................................................................................,102
THE STACK AND THE MOUND..............................................................................................103
MEMORY MANAGEMENT.....................................................................................................103
THE BATTERY........................................................................................................................103
THE MOUND.........................................................................................................................108
ARGUMENTS AND BORROWING...........................................................................................111
A COMPLEX EXAMPLE...........................................................................................................112
WHAT DO OTHER LANGUAGES DO? ....................................................................................118
WHICH TO USE......................................................................................................................119
EXECUTION TIME EFFICIENCY. .............................................................................................119
SEMANTIC IMPACT...............................................................................................................119
TESTS...........................................................................................................,………………………120
THE TEST ATTRIBUTE.............................................................................................................120
THE TESTS MODULE..............................................................................................................127
THE TESTS DIRECTORY...........................................................................................................129
DOCUMENTATION TESTS......................................................................................................131
[CFG (TEST)] ..........................................................................................................................131
CONDITIONAL COMPILATION................................................................................................133
CFG_ATTR..............................................................................................................................135
CFG! ......................................................................................................................................135
DOCUMENTATION................................................................................................................135
ABOUT RUSTDOC..................................................................................................................135
DOCUMENTING SOURCE CODE.............................................................................................136
WRITING DOCUMENTATION COMMENTS............................................................................137
SPECIAL SECTIONS.................................................................................................................138
FN FOO () {}...........................................................................................................139
FN FOO () {}...........................................................................................................139
FN FOO () {}...........................................................................................................140
FN FOO () {}...........................................................................................................140
FN FOO () {}...........................................................................................................141
FN FOO () {}...........................................................................................................141
DOCUMENTING MACROS......................................................................................144
[MACRO_EXPORT] ................................................................................................144
FN MAIN () {}...........................................................................................................145
FN FOO () {}...........................................................................................................145
FN FOO () {}...........................................................................................................146
FN FOO () {}...........................................................................................................146
DOCUMENTATION FEEDBACK
STYLE…………………………………………………………………148
OTHER DOCUMENTATION.....................................................................................148
FN FOO () {}...........................................................................................................149
DOC ATTRIBUTES...................................................................................................149
RE-EXPORTS...........................................................................................................150
CONTROLLING HTML..............................................................................................150
GENERATION OPTIONS...........................................................................................151
SAFETY NOTE...........................................................................................................151
CONSUMERS...........................................................................................................154
ITERATORS...............................................................................................................157
ITERATOR ADAPTERS................................................................................................158
CONCURRENCE..........................................................................................................159
THREADS...................................................................................................................160
MUTABLE STATE SHARED SAFE.................................................................................161
CHANNELS................................................................................................................166
PANICS......................................................................................................................168
ERROR HANDLING.....................................................................................................169
FAIL VS. PANIC...........................................................................................................169
HANDLING ERRORS WITH OPTION AND
RESULT…………………………………………………….173
UNRECOVERABLE ERRORS WITH PANIC! ..................................................................175
PROMOTING PANIC FAILURES....................................................................................175
USING TRY! .................................................................................................................176
FOREIGN / EXTERNAL FUNCTION INTERFACE.............................................................179
INTRODUCTION...........................................................................................................180
CREATING A SECURE INTERFACE.................................................................................182
DESTROYERS................................................................................................................186
CALLBACKS FROM C CODE TO RUST
FUNCTIONS……………………………………………………..187
AIMING CALLBACKS AT RUST
OBJECTS…………………………………………………………………….188
ASYNCHRONOUS CALLBACKS.......................................................................................191
LINK...........................................................................................................,,……………….191
UNSAFE BLOCKS...........................................................................................................193
ACCESSING EXTERNAL GLOBALS...................................................................................193
FOREIGN CALLING CONVENTIONS................................................................................195
INTEROPERABILITY WITH EXTERNAL
CODE……………………………………………………………….196
THE "NULL POINTER OPTIMIZATION"............................................................................197
FEATURES.............................................................................................................................220
COMMENTS..........................................................................................................................221
IF...........................................................................................................................................222
CYCLES...........................................................................................................................224
LOOP..............................................................................................................................224
WHILE.............................................................................................................................225
FOR.................................................................................................................................226
ENDING THE ITERATION EARLY.......................................................................................228
LOOP TAGS......................................................................................................................229
BELONGING.....................................................................................................................230
GOAL...............................................................................................................................230
BELONGING.....................................................................................................................231
MOTION SEMANTICS.......................................................................................................232
THE DETAILS....................................................................................................................233
COPY TYPES.....................................................................................................................234
MORE THAN BELONGING................................................................................................236
REFERENCES AND LOAN..................................................................................................237
GOAL................................................................................................................................237
LOAN................................................................................................................................238
REFERENCES & MUT.........................................................................................................240
THE RULES..........................................................................................................................242
THINKING ABOUT SCOPES.................................................................................................242
PROBLEMS THAT THE LOAN PREVENTS.............................................................................244
ITERATOR INVALIDATION...................................................................................................244
USE AFTER RELEASE...........................................................................................................246
LIFE TIMES..........................................................................................................................249
GOAL..................................................................................................................................250
LIFE TIMES..........................................................................................................................250
IN STRUCT S.......................................................................................................................252
IMPL BLOCKS......................................................................................................................253
MULTIPLE LIFE TIME...........................................................................................................254
THINKING ABOUT SCOPES...................................................................................................255
'STATIC.................................................................................................................................257
LIFE TIMES ELISION..............................................................................................................257
EXAMPLES...........................................................................................................................258
MUTABILITY.........................................................................................................................261
INTERNAL MUTABILITY VS. EXTERIOR
MUTABILITY…………………………………………………………262
FIELD LEVEL MUTABILITY.....................................................................................................264
STRUCTURES........................................................................................................................265
UPDATE SYNTAX...................................................................................................................268
TUPLE STRUCTURES (TUPLE STRUCTS) ................................................................................269
UNIT-LIKE STRUCTS...............................................................................................................271
ENUMERATIONS...................................................................................................................271
CONSTRUCTORS AS FUNCTIONS...........................................................................................273
MATCH..................................................................................................................................274
MAKING MATCH IN ENUMS..................................................................................................276
VECTOR.................................................................................................................................277
ACCESSING ELEMENTS..........................................................................................................278
ITERATING.............................................................................................................................278
CHARACTER STRINGS............................................................................................................279
INDEXED................................................................................................................................281
SLICING..................................................................................................................................282
CONCATENATION..................................................................................................................283
GENERIC................................................................................................................................283
GENERIC FUNCTIONS............................................................................................................285
GENERIC STRUCTURES.......................................................................................................286
TRAITS................................................................................................................................287
STATIC DISPATCH..................................................................................................................309
DYNAMIC DISPATCH.............................................................................................................311
WHY POINTERS? ...................................................................................................................313
REPRESENTATION..................................................................................................................313
OBJECT SECURITY. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . …………...318
CLOSURES..............................................................................................................................319
SYNTAX..................................................................................................................................319
CLOSURES AND ITS SURROUNDINGS....................................................................................320
CLOSURES MOVE...................................................................................................................323
IMPLEMENTATION OF CLOSURES.........................................................................................324
RECEIVING CLOSURES AS ARGUMENTS................................................................................326
FUNCTION POINTERS AND CLOSURES. . . . . . . . . . . . . . . . . . ... . . .. . . . . . . . . . . . . …………..
.328
RETURNING CLOSURES. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 329
The big difference from C and C++ is that Rust is safe by default; all
memory accesses are checked. It is not possible to corrupt memory by
accident.
This tutorial assumes that you have Rust installed locally. Fortunately this is
very straightforward.
I would recommend getting the default stable version; it's easy to download
unstable versions later and to switch between.
This gets the compiler, the Cargo package manager, the API documentation,
and the Rust Book. The journey of a thousand miles starts with one step, and
this first step is painless.
rustup is the command you use to manage your Rust installation. When a new
stable release appears, you just have to say rustup update to upgrade. rustup
doc will open the offline documentation in your browser.
You will probably already have an editor you like, and basic Rust support is
good. I'd suggest you start out with basic syntax highlighting at first, and
work up as your programs get larger.
Personally I'm a fan of Geany which is one of the few editors with Rust
support out-of-the-box; it's particularly easy on Linux since it's available
through the package manager, but it works fine on other platforms.
The main thing is knowing how to edit, compile and run Rust programs. You
learn to program with your fingers; type in the code yourself, and learn to
rearrange things efficiently with your editor.
Is Rust a language that you would be interested in? Let's examine a few small
code examples that demonstrate some of its strengths.
The main concept that makes Rust unique is called 'ownership'. Consider this
little example:
fn main ( ) {
let mut x = vec! [ "Hello" , "world" ];
}
This program creates a variable called x . The value of this variable is Vec
<T> , a 'vector', which we create through a macro defined in the standard
library. This macro is called vec , macros are invoked with a ! . All this
following a general principle in Rust: make things explicit. Macros can do
significantly more complex things than function calls, which is why they are
visually different. The ! It also helps parsing, making writing tools easier,
which is also important.
We have used mut to make x mutable: In Rust the variables are immutable
by default. Later in this example we will be mutating this vector.
It's important to mention that we don't need a type annotation here: while
Rust is statically typed, we don't need to explicitly annotate the type. Rust has
type inference to balance the power of static typing with the verbosity of type
annotations.
Rust prefers memory allocation from the stack than from the mound: x is put
directly on the stack. However, the Vec <T> type allocates space for the vector
elements on the mound. If you are not familiar with this distinction you can
ignore it for now or take a look at 'The Stack and the Mound' . Rust as a
system programming language gives you the ability to control how memory
is allocated, but how we are getting started is not that relevant.
fn main ( ) {
let mut x = vec! [ "Hello" , "world" ];
let y = & x [ 0 ];
}
Let's add a third line. This line looks innocent but causes a compilation error:
fn main ( ) {
let mut x = vec! [ "Hello", "world"];
x.push ("foo");
}
}
^
Whoa! The Rust compiler can sometimes provide well-detailed errors and
this time one of them. As the error explains, while we make the variable
mutable we cannot call push . This is because we already have a reference to
an element of the vector, y . Mutating something while there is a reference to
it is dangerous, because we can invalidate the reference. In this specific case,
when we create the vector, we have only allocated space for two elements.
Adding a third party would mean allocating a new memory segment for all
items, copying all previous values, and updating the internal pointer to that
memory. All of that is fine. The problem is that and would not be updated,
generating a 'hanging pointer'. Which is wrong. Any use of y would be a
mistake in this case, and the compiler has warned us against it.
So how do we solve this problem? There are two approaches that we could
take. The first is to make a copy instead of a reference:
fn main ( ) {
let mut x = vec! [ "Hello" , "world" ];
fn main ( ) {
let mut x = vec! [ "Hello" , "world" ];
{
let y = & x [ 0 ];
}
x.push ( "foo" );
}
With the additional pair of keys we have created an internal scope. and it will
go out of scope before we call push ( ) , so no problem.
This first section of the book will walk you through Rust and his tools. First,
we will install Rust. Then the classic program 'Hello World'. Finally, we'll
talk about Cargo, Rust's package manager and build system.
Hello World!
Now that you have installed Rust, let's write your first program. It is tradition
that your first program in any language is one that prints the text "Hello,
world!" to the screen. The good thing about starting with such a simple
program is that you verify not only that the compiler is installed, but that it is
working. Printing information to the screen is a very common thing.
The first thing we must do is create a file where we can put our code. I like to
create a projects directory in my user directory and keep all my projects there.
Rust doesn't care where the code resides.
This leads to another issue to be clarified: this guide will assume that you
have a basic familiarity with the command line. Rust does not demand
anything regarding your editing tools or where your code lives. If you prefer
an IDE to the command line interface, you should probably take a look at
SolidOak , or wherever the plugins are for your favorite IDE. There are a
number of extensions of varying quality under development by the
community. The team behind Rust also publishes plugins for various
publishers . Setting up your editor or IDE is beyond the objectives of this
tutorial, check the documentation for your specific configuration.
If you're on Windows and you're not using PowerShell, the ~ might not
work. Consult the specific documentation for your terminal for more details.
Let's create a new source code file. We will call our file main.rs. Rust files end
with the extension .rs . If you are using more than one word in your file
name, use a sub-hyphen: hello_world.rs instead of helloworld.rs .
Now that you have your file open write this in:
fn main ( ) {
println! ( "Hello, world!" );
}
Save the changes to the file, and write the following in your terminal
window:
$ rustc main.rs
$. / main # or main.exe in Windows
Hello World!
fn main ( ) {
}
These lines define a function in Rust. The main function is special: it is the
beginning of every Rust program. The first line says: "I am declaring a
function called main which receives no arguments and returns nothing." If
there were arguments, they would be enclosed in parentheses ( ( and ) ), and
since we are not returning anything from this function, we can omit the return
type entirely. We will come to this later.
You will also notice that the function is wrapped in curly braces ( { and } ).
Rust requires these keys delimiting all function bodies. It is also considered a
good style to place the opening brace on the same line as the function
declaration, with an intermediate space.
The second point is the println! ( ) Part . This is calling a Rust macro , which is
how metaprogramming is done in Rust. If this were a function instead, it
would look like this: println ( ) . For our purposes, we need not worry about
this difference. Just know that sometimes you will see ! , and that this means
that you are calling a macro instead of a normal function. Rust implements
println! as a macro instead of a function for good reason, but that's an advanced
topic. One last thing to mention: Rust macros are different from C macros, if
you've used them. Don't be scared of using macros. We'll get to the details
eventually, for now you just have to trust us.
Finally, compile and run our program. We can compile with our rustc
compiler by passing it the name of our source file:
$ rustc main.rs
This is similar to gcc or clang , if you come from C or C . Rust will output an
executable binary. You can see it with ls :
$ ls
main main.rs
Or in Windows:
$ dir
main.exe main.rs
There are two files: our source code, with the extension .rs , and the
executable ( main.exe in Windows, main in others)
Congratulations, you have officially written a Rust program. That makes you
a Rust programmer! Welcome. ������
Next I would like to introduce you to another tool, Cargo, which is used to
write Rust programs for the real world. Just using rustc is fine for simple
things, but as your project grows, you will need something to help you
manage all the options it has, as well as making it easy to share the code with
other people and projects.
Hello, Cargo!
Cargo is a tool that Rusteros use to help them manage their Rust projects.
Cargo is currently in pre-1.0 status, and as a result is still a work in progress.
However it is already good enough for many Rust projects, and it is assumed
that Rust projects will use Cargo from the beginning.
If you have installed Rust through the official installers then you should have
Cargo. If you have installed Rust in any other way, you could take a look at
Cargo's README for specific instructions on how to install it.
Migrating in Charge
Let's convert Hello World in Charge.
To load our project, we need two things: Create a Cargo.toml configuration file
, and put our source file in the right place. Let's do that part first:
$ mkdir src
$ mv main.rs src / main.rs
Note that because we are creating an executable, use main.rs . If we wanted to
create a library, you should use lib.rs . Customized locations for the entry
point can be specified with a [ [[lib]] or [[bin]] ] crates-custom key in the
TOML file described below.
Cargo expects your source code files to reside in the src directory . This
leaves the root level for other things, like READMEs, licensing information,
and anything unrelated to your code. Cargo helps us keep our projects nice
and orderly. A place for everything, and everything in its place.
$ editor Cargo.toml
Make sure you have this correct name: you need the capital C !
[package]
name = "hello_world"
version = "0.0.1"
authors = [ "Your name <your@example.com> " ]
Once you have this file in its place, we should be ready to compile! Try this:
$ build charge
Compiling hello_world v0. 0.1 (file: /// home / yourname / projects /
hello_world)
$. / target / debug / hello_world
Hello World!
Bam! We build our project with build charge , and run it with . / target / debug /
hello_world . We can do the two steps in one with run charge :
$ charge run
Running `target / debug / hello_world`
Hello World!
Note that we did not rebuild the project this time. Cargo determined that we
hadn't changed the source file, so I just run the binary. If we had made a
modification, we should have seen it doing the two steps:
$ charge run
Compiling hello_world v0. 0.1 (file: /// home / yourname / projects /
hello_world)
Running `target / debug / hello_world`
Hello World!
This has not contributed much to us beyond our simple use of rustc , but think
about the future: when our projects get more complicated, we will need to do
more to get all the parts to compile correctly. With Cargo, as our project
grows, we simply run cargo build , and everything will work correctly.
When our project is finally ready for release, you can use cargo build --release to
compile it with optimizations.
You may also have noticed that Cargo has created a new file: Cargo.lock .
[root]
name = "hello_world"
version = "0.0.1"
This file is used by Cargo to keep track of the dependencies used in your
application. For now, we don't have any, and it's a little scattered. You should
never need to touch this file on your own, just let Cargo handle it.
That's it! We have successfully built hello_world with Cargo. Although our
program is simple, it is using much of the real tools that you will use for the
rest of your career with Rust. You can assume that to start virtually any Rust
project you will do the following:
$ git clone someturl.com/foo
$ cd foo
$ build charge
A New Project
You don't have to go through that entire process every time you want to start
a new project! Cargo has the ability to create a template directory in which
you can start developing immediately.
$ cd hello_world
$ tree.
.
├── Cargo.toml
└── src
└── main.rs
1 directory, 2 files
If you don't have the tree command installed, you could probably get it using
your distribution's package handler. It is not necessary, but it is certainly
useful.
This is all you need to get started. Let's first look at our Cargo.toml :
[package]
name = "hello_world"
version = "0.0.1"
authors = [ "Your Name <your@example.com>" ]
I loaded this file with default values based on the arguments you provided
and your global git settings . You might also notice that Cargo has initialized
the hello_world directory as a git repository .
fn main ( ) {
println! ( "Hello, world!" );
}
Cargo has generated a "Hello world!" for us, you're ready to start rubbing
elbows. Cargo has its own guide which covers all its features in much more
depth.
Now that we have learned the tools, let's actually start learning more about
Rust as a language. This is the base that will serve you well for the rest of
your time with Rust.
You have two options: Immerse yourself in a project with ' Learn Rust ', or
start from the bottom and work upwards with ' Syntax and Semantics '. More
experienced system programmers will probably prefer 'Learn Rust', while
those from dynamic languages might also enjoy it. Different people learn
differently! Choose what works best for you.
Chapter I
Learn Rust
Welcome! This section has a few tutorials that will teach you Rust through
project building. You'll get an overview, but we'll also take a look at the
details.
If you prefer a more bottom-up experience, check out Syntax and Semantics .
Guessing Game
For our first project, we will implement a classic programming problem for
beginners: a guessing game. How the game works: Our program will
generate a random integer between one and one hundred. It will ask us to
introduce a hunch. After having provided our number, it will tell us if we
were far below and far above. Once we guess the correct number, you will
congratulate us. Sounds good?
Initial setup
Let's create a new project. Go to your project directory. Remember how we
created our directory structure and a Cargo.toml for hello_world ? I have a
command that does that for us. Let's try it:
$ cd ~ / projects
$ charge new riddles --bin
$ cd riddles
We pass the name of our project in charge new , along with the flag --bin ,
because we are creating a binary, instead of a library.
[package]
name = "riddles"
version = "0.1.0"
authors = [ "Your Name <your@example.com>" ]
Cargo obtains this information from your environment. If it is not correct,
correct it.
Finally, Cargo has generated a 'Hello, world!' for us. Take a look at src / main.rs
:
fn main () {
println! ( "Hello, world!" );
}
Let's try to compile what Cargo has provided us:
$ build charge
Compiling riddles v0.1.0 (file: /// home / you / projects / riddles)
Excellent! Open your src / main.rs again. We will be writing all our code in this
file.
Before continuing, let me show you one more command from Cargo: run .
cargo run is a kind of cargo build , but with the difference that it also executes the
binary produced. Let's try it:
$ charge run
Compiling riddles v0. 1.0 (file: /// home / you / projects / riddles)
Running `target / debug / riddles`
Hello World!
Great! The run command is very useful when you need to quickly iterate
through a project. Our game is one of those projects, we will need to quickly
test each iteration before moving on to the next.
fn main () {
println! ("Guess the number!");
fn main () {
As you have seen previously, the main () function is the entry point to your
program. The fn syntax declares a new function, the () s indicate that there
are no arguments, and { the body of the function begins. Because we don't
include a return type, it is assumed to be () an empty tuple .
For example, they are immutable by default. This is why our example uses
mut : this does a mutable binding, rather than immutable. let doesn't just take
a name from the left side, let accepts a ' pattern '. We will use the patterns a
little later. It is sufficient for now to use:
So we know that let mut hunch will introduce a mutable binding called hunch ,
but we have to look on the other side of = to know what it is being associated
with: String :: new () .
Let's continue:
io :: stdin ()
Remember how we use use in std :: io in the first line of our program? We are
now calling an associated function in std :: io . If you had not used use std :: io ,
we could have written this line as std :: io :: stdin () .
The next part will use that handle to get user input:
We are not done with this line yet. While it is a single line of text, it is just
the first part of a complete logical line of code:
.okay()
.expect ("Line reading failure");
When you call a method with the .foo () syntax you can enter a line break and
another space. This helps you divide long lines. We could have written:
io :: stdin (). read_line (& mut hunched) .ok (). expect ("Line reading
failed");
But that is more difficult to read. So we have divided it into three lines for
three method calls. We've already talked about read_line () , but what about ok
() and expect () ? Well, we already mentioned that read_line () places the user's
input in the & mut String that we provide. But it also returns a value: in this
case an io :: Result . Rust has a number of types called Result in its standard
library: a generic Result , and specific versions for sub-libraries, such as io ::
Result .
If we remove the calls to those two methods, our program will compile, but
we will get a warning:
$ build charge
Compiling riddles v0. 1.0 (file: /// home / you / projects / riddles)
src / main.rs: 10 : 5 : 10 : 44 warning: unused result which must be used,
# [warn (unused_must_use)] on by default
src / main.rs: 10 io :: stdin (). read_line (& mut hunch);
^ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Rust advises us that we have not used the Result value . This warning comes
from a special annotation that io :: Result has . Rust is trying to tell you that you
have not handled a possible error. The correct way to suppress the error is to
actually write the code for handling errors. Luckily, if we only want to end
the program execution if there is a problem, we can use these two small
methods. If we could somehow recover from the error, we would do
something different, but leave that for a future project.
let x = 5 ;
let y = 10 ;
Either way, that's the tour. We can run it with run charge :
$ charge run
Compiling guessing_game v0. 1.0 (file: /// home / you / projects /
riddles)
Running `target / debug / riddles`
Guess the number!
Please enter your hunch.
6
Your hunch was: 6
Congratulations! Our first part is over: we can get keyboard input and print it
back.
Using external crates is where Cargo really shines. Before we can write code
that uses rand , we must modify our Cargo.toml file . Open it, and add these
lines at the end:
[dependencies]
rand = "0.3.0"
Now, without changing anything in our code, let's build our project:
$ build charge
Updating registry `https: // github.com / rust-lang / crates.io-index`
Downloading rand v0. 3.8
Downloading libc v0. 1.6
Compiling libc v0. 1.6
Compiling rand v0. 3.8
Compiling riddles v0. 1.0 (file: /// home / you / projects / riddles)
(You could see different versions, of course.)
After updating the registry, Cargo checks our dependencies (in [dependencies] )
and downloads them if they do not have them yet. In this case we just said we
wanted to rely on rand , and we also got a copy of libc . This is because rand
itself depends on libc to function. After downloading the dependencies, Cargo
compiles them, to later compile our code.
$ build charge
That's right, there is no way out! Cargo knows that our project has been built,
as well as all its dependencies, so there is no reason to do the whole process
again. With nothing to do, just run. If we open src / main.rs again, we make a
trivial change, save the changes, we would only see one line:
$ build charge
Compiling riddles v0. 1.0 (file: /// home / you / projects / riddles)
So, we have told Cargo that we wanted any version 0.3.x of rand , and he
downloaded the latest version at the time of writing this tutorial, v0.3.8 . But
what happens when the next version v0.3.9 is released with a major bugfix?
While receiving bugfixes is important, what happens if 0.3.9 contains a
regression that breaks our code?
The answer to this problem is the Cargo.lock file, which you will find in your
project directory. When you build your project the first time, cargo
determines all versions that match your criteria and writes them to the
Cargo.lock file . When you build your project in the future, Cargo will notice
that a Cargo.lock file exists, and will use the versions specified in it, instead of
doing all the work of determining the versions again. This allows you to have
an automatically reproducible construction. In other words, we will stay at
0.3.8 until we explicitly upload the version, in the same way the people with
whom we have shared our code will do so, thanks to the Cargo.lock file .
But what happens when we want to use v0.3.9 ? Cargo has another command,
update , which translates to 'ignore the lock and determine all the latest
versions that match what we have specified. If this works, write those
versions to the Cargo.lock ' lock file . But, by default, Cargo will only search
for versions greater than 0.3.0 and less than 0.4.0 . If we want to move to 0.4.x
, we would need to update the Cargo.toml file directly. When we do that, the
next time we run cargo build , Cargo will update the index and re-evaluate our
rand requirements .
There is much more to say about Cargo and its ecosystem , but for now, that's
all we need to know. Cargo makes it really easy to reuse libraries, and
Russetters tend to write small projects which are built by a smaller set of
packages.
fn main () {
println! ("Guess the number!");
The first thing we have done is change the first line. Now it says extern crate
rand . Because we declare rand in our [dependencies] section , we can use extern
crate to let Rust know that we will be using rand . This is equivalent to a use
rand; , so that we can make use of whatever is inside the crate rand through the
prefix rand :: .
Then we have added another use line : use rand :: Rng . In a few moments we
will be using a method, and this requires that Rng is available for it to work.
The basic idea is this: the methods are inside something called 'traits', and for
the method to work you need the trait to be available. For more details go to
the Traits section .
We use the rand :: thread_rng () function to obtain a copy of the random number
generator, which is local to the thread of execution in which we are. Because
we have made rand :: Rng available through the use rand :: Rng , it has a gen_range ()
method available. This method accepts two arguments, and generates a
random number between them. It is inclusive at the lower limit, but it is
exclusive at the upper limit, so we need 1 and 101 to get a number between
one and one hundred.
The second line only prints the secret number. This is useful as we develop
our program so that we can test it. We will be removing this line for the final
version. It's not a game if you print the answer right when you start it!
$ charge run
Compiling riddles v0. 1.0 (file: /// home / you / projects / riddles)
Running `target / debug / riddles`
Guess the number!
The secret number is: 7
Please enter your hunch.
4
Your hunch was: 4
$ charge run
Running `target / debug / riddles`
Guess the number!
The secret number is: 83
Please enter your hunch.
5
Your hunch was: 5
Gradious! Next: let's compare our riddle with the secret number.
Comparing riddles
Now that we have user input, let's compare the riddle with our secret number.
Here is our next step, although it still doesn't work completely:
fn main () {
println! ("Guess the number!");
enum foo {
Pub,
Baz,
}
With this definition, anything of the Foo type can be either a Foo :: Bar or a
Foo :: Baz . We use the :: to indicate the namespace for a particular enum
variant .
The Ordering enum has three possible variants: Less , Equal , and Greater
(minor, equal and major respectively). The match statement takes a value of a
type, and allows you to create an 'arm' for each possible value. Because we
have three possible types of Ordering , we have three arms:
Earlier I mentioned that it still doesn't work. Let's put it to the test:
$ build charge
Compiling riddles v0. 1.0 (file: /// home / you / projects / riddles)
src / main.rs: 28 : 21 : 28 : 35 error: mismatched types:
expected `& collections :: string :: String`,
found `& _`
(expected struct `collections :: string :: String`,
found integral variable) [E0308]
src / main.rs: 28 match hunch.cmp (& secret_number) {
^ ~~~~~~~~~~~~~
error: aborting due to previous error
Could not compile `riddles`.
Oops! A big mistake. The main thing in it is that we have 'mismatched types'.
Rust has a strong, static rate system. However, it also has type inference.
When we typed let hunch = String :: new () , Rust was able to infer that hunch must
be a String , and so did not make us write the type. With our secret_number ,
there are a number of types that can have a value between one and one
hundred: i32 , a thirty-two-bit number, u32 , an unsigned thirty-two-bit
number, or i64 a sixty-four-bit number or others. So far, that hasn't mattered,
since Rust by default uses i32 . However, in this case, Rust doesn't know how
to compare hunch to secret_number . Both need to be of the same type. In the
end, we want to convert the String that we read as input into a real type of
number, for comparison purposes. We can do that with three more lines. Here
is our new program:
extern crate rand;
fn main () {
println! ("Guess the number!");
let secret_number = rand :: thread_rng (). gen_range (1, 101);
Like read_line () , our call to parse () could cause an error. What if our character
string contains A ߑ ? There would be no way to convert that to a number.
This is why we will do the same as we did with read_line () : use the ok () and
expect () methods to end abruptly if there are any errors.
$ charge run
Compiling riddles v0. 1.0 (file: /// home / you / projects / riddles)
Running `target / riddles`
Guess the number!
The secret number is: 58
Please enter your hunch.
76
Your hunch was: 76
Very big!
Excellent! You can see that I have even added spaces before my attempt, and
still the program determined that I try 76. Run the program a few times, and
verify that guessing the number works, as well as trying a very small number.
Now we have most of the game working, but we can only try to guess once.
Let's try to change that by adding cycles!
Iteration
The loop keyword provides an infinite loop. Let's add it:
Guess the number! The secret number is: 58 Please enter your riddle. 76
Your hunch was: 76 Very big!
fn main () {
println! ("Guess the number!");
loop {
println! ("Please enter your hunch.");
fn main () {
println! ("Guess the number!");
loop {
println! ("Please enter your hunch.");
fn main () {
println! ("Guess the number!");
loop {
println! ("Please enter your hunch.");
fn main () {
println! ("Guess the number!");
loop {
println! ("Please enter your hunch.");
This first project taught you a lot: let , match , methods, associated functions,
using external crates, and more. Our next project will demonstrate even more.
For our second project, let's take a look at a classic concurrency problem. It's
called 'The Dinner of the Philosophers'. It was originally conceived by
Dijkstra in 1965, but we will use a slightly adapted version of this paper by
Tony Hoare in 1985.
This classic problem exhibits some elements of concurrency. The reason for
this is that it is an effectively difficult solution to implement: a simple
implementation can generate a deadlock. For example, consider a simple
algorithm that could solve this problem:
There are different ways to solve this problem. We will guide you through
the solution of this tutorial. For now, let's start by modeling the problem.
Let's start with the philosophers:
struct Philosopher {
name: String ,
}
impl Filosofo {
fn new (name: & str ) -> Philosopher {
Philosopher {
name: name.to_string (),
}
}
}
fn main () {
let f1 = Philosopher :: new ( "Judith Butler" );
let f2 = Philosopher :: new ( "Gilles Deleuze" );
let f3 = Philosopher :: new ( "Karl Marx" );
let f4 = Philosopher :: new ( "Emma Goldman" );
let f5 = Philosopher :: new ( "Michel Foucault" );
}
Here, we create a structure (struct) to represent a philosopher. For now the
name is all we need. We choose the String type for the name, instead of & str .
Generally speaking, working with the type that owns (owns) your data is
easier than working with one that uses references.
Let's continue:
# struct Philosopher {
# name: String ,
#}
impl Filosofo {
fn new (name: & str ) -> Philosopher {
Philosopher {
name: name.to_string (),
}
}
}
This impl block allows us to define things in Philosopher structures . In this
case we are defining an 'associated function' called new . The first line looks
like this:
# struct Philosopher {
# name: String ,
#}
# impl Philosopher {
fn new (name: & str ) -> Philosopher {
# Philosopher {
# name: name.to_string (),
#}
#}
#}
We get an argument, name , of type & str . A reference to another character
string. This returns an instance of our Philosopher structure .
# struct Philosopher {
# name: String ,
#}
# impl Philosopher {
# fn new (name: & str ) -> Philosopher {
Philosopher {
name: name.to_string (),
}
#}
#}
The above creates a new Philosopher , and assigns our name argument to the
name field . Not the argument itself, because we call .to_string () on it. Which
creates a copy of the string pointed to our & str , and gives us a new String ,
which is the type of the field name of Filosofo .
Why not accept a String directly? It is easier to call. If we received a String but
the caller had a & str they would be forced to call .to_string () on their side. The
downside to this flexibility is that we always make a copy. For this little
program, this is not particularly important, and we know that we will be using
short strings anyway.
One last thing you may have noticed: we only define a Philosopher , and we
don't seem to do anything with it. Rust is an 'expression-based' language,
which means that almost anything in Rust is an expression that returns a
value. This is true for functions too, the last expression is automatically
returned. Because we create a new Philosopher as the last expression of this
function, we end up returning it.
The name new () is nothing special to Rust, but it is a convention for functions
that create new instances of structures. Before we talk about why, we take a
look at main () again:
# struct Philosopher {
# name: String ,
#}
#
# impl Philosopher {
# fn new (name: & str ) -> Philosopher {
# Philosopher {
# name: name.to_string (),
#}
#}
#}
#
fn main () {
let f1 = Philosopher :: new ( "Judith Butler" );
let f2 = Philosopher :: new ( "Gilles Deleuze" );
let f3 = Philosopher :: new ( "Karl Marx" );
let f4 = Philosopher :: new ( "Emma Goldman" );
let f5 = Philosopher :: new ( "Michel Foucault" );
}
Here, we create five variables with five new philosophers. These are my five
favorites, but you can replace them with whomever you prefer. If you had not
defined the new () function , main () would look like this:
# struct Philosopher {
# name: String ,
#}
fn main () {
let f1 = Philosopher {name: "Judith Butler" .to_string ()};
let f2 = Philosopher {name: "Gilles Deleuze" .to_string ()};
let f3 = Philosopher {name: "Karl Marx" .to_string ()};
let f4 = Philosopher {name: "Emma Goldman" .to_string ()};
let f5 = Philosopher {name: "Michel Foucault" .to_string ()};
}
A little louder. Using new also has other advantages, but even in this simple
case it ends up being of better use.
Now that we have the basics in place, there are a number of ways in which
we can attack the larger problem. I like to start at the end: let's create a way
for each philosopher to finish eating. As a small step, let's make a method,
and then iterate through all the philosophers calling it:
struct Philosopher {
name: String ,
}
impl Filosofo {
fn new (name: & str ) -> Philosopher {
Philosopher {
name: name.to_string (),
}
}
fn main () {
let philosophers = vec! [
Philosopher :: new ( "Judith Butler" ),
Philosopher :: new ( "Gilles Deleuze" ),
Philosopher :: new ( "Karl Marx" ),
Philosopher :: new ( "Emma Goldman" ),
Philosopher :: new ( "Michel Foucault" ),
];
In the body of the loop, we call f.comer (); , which is defined as:
Very easy, everyone has finished eating! But we haven't implemented the real
problem yet, so we're not done yet!
Next, we not only want to just finish eating, but actually eat. Here is the next
version:
use std :: thread;
struct Philosopher {
name: String ,
}
impl Filosofo {
fn new (name: & str ) -> Philosopher {
Philosopher {
name: name.to_string (),
}
}
fn main () {
let philosophers = vec! [
Philosopher :: new ( "Judith Butler" ),
Philosopher :: new ( "Gilles Deleuze" ),
Philosopher :: new ( "Karl Marx" ),
Philosopher :: new ( "Emma Goldman" ),
Philosopher :: new ( "Michel Foucault" ),
];
If you run this program, you should see each philosopher eat at once:
Judith Butler is eating.
Judith Butler has finished eating.
Gilles Deleuze is eating.
Gilles Deleuze has finished eating.
Karl Marx is eating.
Karl Marx has finished eating.
Emma Goldman is eating.
Emma Goldman has finished eating.
Michel Foucault is eating.
Michel Foucault has finished eating.
Excellent! We're moving forward. There is only one detail: we are not
operating concurrently, which is central to our problem!
struct Philosopher {
name: String ,
}
impl Filosofo {
fn new (name: & str ) -> Philosopher {
Philosopher {
name: name.to_string (),
}
}
fn main () {
let philosophers = vec! [
Philosopher :: new ( "Judith Butler" ),
Philosopher :: new ( "Gilles Deleuze" ),
Philosopher :: new ( "Karl Marx" ),
Philosopher :: new ( "Emma Goldman" ),
Philosopher :: new ( "Michel Foucault" ),
];
for h in handles {
h.join (). unwrap ();
}
}
All we have done is change the loop in main () , and added a second! This is
the first change:
for h in handles {
h.join (). unwrap ();
}
At the end of main () , we iterate through the handles by calling join () on
them, which blocks execution until the thread has completed execution. This
ensures that the thread completes its execution before the program ends.
If you run this program, you will see that philosophers eat without order! We
have multi-threads!
Gilles Deleuze is eating.
Gilles Deleuze has finished eating.
Emma Goldman is eating.
Emma Goldman has finished eating.
Michel Foucault is eating.
Judith Butler is eating.
Judith Butler has finished eating.
Karl Marx is eating.
Karl Marx has finished eating.
Michel Foucault has finished eating.
But that about the forks we have not fully modeled them yet.
struct Table {
forks: Vec <Mutex <() >>,
}
This table contains a Mutex vector . A mutex is a way to control concurrency,
only one thread can access content at a time. This is exactly the property we
need for our holders. We use an empty pair, () , inside the mutex, because we
are not going to use the value, we will just hold on to it.
struct Philosopher {
name: String ,
left: usize,
right: usize,
}
impl Filosofo {
fn new (name: & str , left: usize, right: usize) -> Philosopher {
Philosopher {
name: name.to_string (),
left: left,
right: right,
}
}
struct Table {
forks: Vec <Mutex <() >>,
}
fn main () {
let table = Arc :: new (Table {forks: vec! [
Mutex :: new (()),
Mutex :: new (()),
Mutex :: new (()),
Mutex :: new (()),
Mutex :: new (()),
]});
for h in handles {
h.join (). unwrap ();
}
}
Many changes! However, with this iteration, we have obtained a functional
program. Let's see the details:
struct Philosopher {
name: String,
left: usize,
right: usize,
}
We are going to need to add two more fields to our Philosopher structure . Each
philosopher will have two forks: the one on the left and the one on the right.
We will use the type usize to indicate them, because this is the type with
which the vectors are indexed. These two values will be indexes in the holders
that our Table owns.
fn new (name: & str, left: usize, right: usize) -> Philosopher {
Philosopher {
name: name.to_string (),
left: left,
right: right,
}
}
Now we need to build those values left and right , so that we can add them to
new () .
fn eat (& self, table: & table) {
let _left = table.forks [self.left] .lock (). unwrap ();
let _right = table.forks [self.right] .lock (). unwrap ();
The call to lock () may fail, and if it does, we want to end it abruptly. In this
case the error that can occur is that the mutex this 'poisoning' ( 'poisoned'),
which is what happens when the thread does panic while holding the lock.
Because this shouldn't happen, we simply use unwrap () .
Another strange thing about these lines: we have named the results _ left and
_ right . What about that sub-script? Well, we don't actually plan to use the
value inside the lock. We just want to acquire it. As a consequence, Rust will
warn us that we never use value. Through the use of the sub-script we tell
Rust what we wanted, that way he would not generate the warning.
What about releasing the lock ?, Well, this will happen when _left and _right
go out of scope, automatically.
With all this, our program works! Only two philosophers can eat at any given
time and consequently you will have an exit that will look like this:
Gilles Deleuze is eating.
Emma Goldman is eating.
Emma Goldman has finished eating.
Gilles Deleuze has finished eating.
Judith Butler is eating.
Karl Marx is eating.
Judith Butler has finished eating.
Michel Foucault is eating.
Karl Marx has finished eating.
Michel Foucault has finished eating.
Congratulations! You have implemented a classic concurrency problem in
Rust.
The problem
There are many problems that we could have chosen, but we will choose an
example in which Rust has a clear advantage over other languages: numerical
computing and threads.
Second, many languages have a 'global interpreter lock' (GIL), which limits
concurrency in many situations. This is done in the name of security, which is
a positive effect, but limits the amount of work that can be done concurrently,
which is a huge negative.
To emphasize these 2 aspects, we will create a small project that uses these
two aspects to a great extent. Because the focus of the example is to embed
Rust in other languages, instead of the problem itself, we will use a toy
example:
Start ten threads. Within each thread, it counts from one to five million. After
all threads have finished, print "completed!".
5_000_000 .times do
count = 1
end
end
end
threads.each {| t | t.join}
puts "completed!"
Try running this example, and pick a number that runs for a few seconds.
Depending on the hardware of your computer, you will have to increase or
decrease the number.
While it is true that you are in a synthetic program, one could imagine many
problems similar to this in the real world. For our purposes, picking up a few
threads and occupying them represents a kind of parallel and expensive
computing.
A Rust library
Let's write this problem in Rust. First, let's create a new project with Cargo:
$ charge new embed
$ cd embed
This program is easy to write in Rust:
use std :: thread;
fn process () {
let handles: Vec <_> = ( 0 .. 10 ) .map (| _ | {
thread :: spawn (|| {
let mut _x = 0 ;
for _ in ( 0 .. 5_000_000 ) {
_x =1
}
})
}). collect ();
for h in handles {
h.join (). ok (). expect ( "A thread could not be joined!" );
}
}
Some of this should look familiar to previous examples. We start ten threads,
collecting them in a vector handles . Within each thread, we iterate five
million times, adding one to _x on each iteration. Why the sub-script? Well,
if we remove it and then compile:
$ build charge
Compiling embed v0. 1.0 (file: /// Users / goyox86 / Code / rust / embed)
src / lib.rs: 3 : 1 : 16 : 2 warning: function is never used: ` process` , #
[warn (dead_code)] on by default
src / lib.rs: 3 fn process () {
src / lib.rs: 4 let handles: Vec <_> = ( 0 .. 10 ) .map (| _ | {
src / lib.rs: 5 thread :: spawn (|| {
src / lib.rs: 6 let mut x = 0 ;
src / lib.rs: 7 for _ in ( 0 .. 5 _000_000) {
src / lib.rs: 8 x =1
...
src / lib.rs: 6 : 17 : 6 : 22 warning: variable `x` is assigned to, but never
used, # [warn (unused_variables)] on by default
src / lib.rs: 6 let mut x = 0 ;
^ ~~~~
The first warning is due is due to building a library. If we had a test for this
function, the warning would disappear. But for now it is never called.
So far, however, it is a Rust library, and it doesn't expose anything that can be
called from C. If we wanted to connect it to another language, in its current
state, it wouldn't work. We just need to make a few small changes to fix it.
The first thing is to modify the principle of our code:
# [no_mangle]
pub extern fn process () {
We must add a new attribute, no_mangle . When we create a Rust library, it
changes the name of the function in the compiled output. The reasons for this
are beyond the scope of this tutorial, but in order for other languages to know
how to call the function, we must prevent the compiler from changing the
name in the compiled output. This attribute disables that behavior.
The other change is the pub extern . The pub means that this function can be
called from outside this module, and the extern says that it can be called from
C. That's it! Not many changes.
The second thing we need to do is change a setting in our Cargo.toml . Add this
at the end:
[lib]
name = "embed"
crate-type = [ "dylib" ]
These lines inform Rust that we want to compile our library into a standard
dynamic library. Rust compiles an 'rlib', a specific format from Rust.
Now that we have our Rust library, let's use it from Ruby.
Ruby
Create an embeber.rb file inside our project, and put this inside:
require 'ffi'
Hello module
extend FFI :: Library
ffi_lib 'target / release / libembeber.dylib'
attach_function : process , [] ,: void
end
Hello .process
puts 'completed!'
Before we can run it, we need to install the ffi gem :
$ gem install ffi # this may need sudo
Fetching: ffi- 1.9 . 8 .gem ( 100 %)
Building native extensions. This could take a while ...
Successfully installed ffi- 1.9 . 8
Parsing documentation for ffi- 1.9 . 8
Installing ri documentation for ffi- 1.9 . 8
Done installing documentation for ffi after 0 seconds
1 gem installed
Finally, let's try running it:
$ ruby embeber.rb
completed!
$
Whoa, that was fast! On my system, it takes 0.086 seconds, as opposed to two
seconds than the pure Ruby version. Let's analyze this Ruby code:
require 'ffi'
First we need to require the ffi gem . It allows us to interact with a Rust
library like a C library.
Hello module
extend FFI :: Library
ffi_lib 'target / release / libembeber.dylib'
The Hello module is used to attach the native functions of the shared library.
Inside, we extend the FFI :: Library module and then call the ffi_lib method to
load our shared object library. We simply pass the path in which our library is
stored, which, as we saw earlier, is target / release / libembeber.dylib .
Hello .process
This is the call to Rust. The combination of our module and the call to
attach_function have configured everything. It looks like a Ruby method but it is
actually Rust code!
puts 'completed!'
Finally, and as a requirement of our project, we print completed! .
That's it! As we have seen, bridging the two languages is really easy, and it
buys us a lot of performance.
Next, let's try Python!
Python
Create an embeber.py file in this directory, and put this in:
lib.process ()
print ( "completed!" )
Even easier! We use cdll from the ctypes module . A quick call to LoadLibrary
later, and then we can call process () .
Node.js
Node is not a language, but it is currently the dominant server-side Javascript
implementation.
lib.process ();
console .log ( "completed!" );
It looks more like the Ruby example than the Python example. We use the ffi
module to access ffi.Library () , which allows us to load our shared object
library. We need to note the return type and the types of the function's
arguments, which are void for the return and an empty array to represent no
arguments. From there we simply call the process () function and print the
result.
So, you've learned how to write some Rust code. But there is a difference
between writing any Rust code and writing good Rust code.
This section consists of relatively independent tutorials that show you how to
take your Rust to the next level. Common patterns and characteristics of the
standard library will be presented. You can read these sections in the order
you prefer.
fn main () {
let x = 42 ;
foo ();
}
This program has three variables in total: two in foo () , one in main () . Just
like before, when main () is called, a single integer is assigned for its
activation record. But before we demonstrate what happens when foo () is
called, we need to visualize what is happening in memory. Your operating
system presents your program with a very simple vision: an immense list of
addresses, from 0 to a very large number, which represents how much RAM
the machine has. For example if you have a gigabyte of RAM, your addresses
will go from 0 to 1,073,741,824 , a number that comes from 2 , the number of
30
bytes in a gigabyte.
This memory is a kind of giant arrangement: addresses start at zero and
increase to the final number. So here's a diagram of our first activation
record:
AddressNameValue
0 x 42
We have placed x in address 0 , with the value 42
When foo () is called a new activation record is assigned:
AddressNameValue
two z 100
one and 5
0 x 42
Since 0 was reserved for the first frame, 1 and 2 are used for the activation
record of foo () . The stack grows upwards, as we call more functions.
There are some important things to note here. The numbers 0, 1, and 2 exist
for illustrative purposes only, and have no relation to the numbers that a
computer would actually use. In particular, the series of addresses are
separated by a number of bytes, and that separation may even exceed the size
of the value being stored.
After foo () ends, your activation record is released:
AddressNameValue
0 x 42
And then after main () ends, this last value goes away. Easy!
It is called a stack because it works like a stack of plates: the first plate you
place is the last plate you will remove. Stacks are sometimes called 'last in,
first out queues' queues, for these reasons the last value you put in the stack
will be the first you will get from it.
Let's try an example of three levels:
fn bar () {
let i = 6 ;
}
fn foo () {
let a = 5 ;
let b = 100 ;
let c = 1 ;
Pub();
}
fn main () {
let x = 42 ;
foo ();
}
Well, in the first instance, we call main () :
AddressNameValue
0 x 42
Then main () calls foo () :
AddressNameValue
3 c one
two b 100
one to 5
0 x 42
Then foo () calls bar () :
AddressNameValue
4 i 6
3 c one
two b 100
one to 5
0 x 42
Uff! Our stack is growing.
After bar () ends, its activation record is released, leaving only foo () and main
() :
AddressNameValue
3 c one
two b 100
one to 5
0 x 42
Then foo () ends, leaving only main ()
AddressNameValue
0 x 42
We are done then. It is understood? It is like stacking plates: you add to the
top and remove from it.
The Mound
Now, all of this works fine, but not everything works that way. Sometimes,
you need to pass memory between different functions, or keep memory alive
for a longer time than executing a function. For this we use the mound.
In Rust, you can allocate memory from the mound with the type Box <T> .
Here is an example:
fn main () {
let x = Box :: new ( 5 );
let y = 42 ;
}
Here, what happens when main () is called:
AddressName Value
one and 42
0 x ??????
We allocate space for two variables on the stack. y is 42 , as we know so far,
but what about x ? Well, x is a Box <i32> , and the boxes allocate memory
from the mound. The value of the box in question is a structure that has a
pointer to 'the mound'. When function execution begins, and Box :: new () is
called, it allocates some memory for the mound and places 5 there. Memory
now looks like this:
AddressNameValue
2 30 5
... ... ...
one and 42
0 x 2 30
We have 2 on our hypothetical computer with 1GB of RAM. And because
30
our stack grows from scratch, the easiest way to allocate memory is from the
other end. So our first value is in the highest place in memory. And the value
of the structure in x has a flat pointer (raw pointer) to the place we have
assigned on the mound, then the value of x EN 2 , the memory address we
30
have requested.
We haven't talked much about what allocating and releasing memory actually
means in these contexts. Going into the deep detail of it is beyond the scope
of this tutorial, the important thing to note is that the mound is not a simple
pile growing from the opposite side. We will have an example of this later in
the book, but because the mound can be assigned and released in any order, it
can end with 'gaps'. Here is a diagram of the memory distribution of a
program that has been running for some time:
AddressName Value
2 30 5
(2 30 ) - 1
(2 30 ) - 2
(2 30 ) - 3 42
... ... ...
3 and (2 30 ) - 3
two and 42
one and 42
0 x 2 30
In this case, we have assigned four things on the mound, but we have
released two of them. There is a gap between 2 and (2 ) - 3 that is not
30 30
currently being used. The specific detail about how and why this happens
depends on the strategy used to manage the mound. Different programs can
use different 'memory allocators', which are libraries that handle memory
allocation for you. Programs in Rust use jemalloc for that purpose.
Anyway, and back to our example. Because this memory is on the mound, it
can stay alive longer than the function that creates the box. However, in this
case, this does not happen. when the function ends, we need to release the
moving
activation record from main () . Box <T> , however, does have a trick up its
sleeve: Drop . The Drop for Box implementation frees up the memory that has
been allocated when the box is created. Great! So when x leaves (leaves
context), it first frees allocated memory from the mound:
AddressName Value
one and 42
0 x ??????
moving
. We can make memory stay alive longer by transferring ownership, sometimes called 'moving out
of the box'. More complex examples will be covered later. ↩
Then the activation record goes away, freeing up all of our memory.
Arguments and borrowing
We have done some basic examples with the stack and the mound, but what
about function arguments and borrowing? Here's a little Rust program:
fn foo (i: & i32 ) {
let z = 42 ;
}
fn main () {
let x = 5 ;
let y = & x;
foo (y);
}
When we enter main () , the memory looks like this:
Address Name Value
one and 0
0 x 5
Activation records are not just for local variables, they are also for arguments.
In this case, we need to have both i , our argument, and z our local variable.
i is a copy of the argument, and . Because the value of y is 0 then that is the
value of i .
This is one reason why borrowing a variable doesn't free up any memory: the
reference value is just a pointer to a memory address. If we got rid of the
underlying memory, things would not go quite right.
A complex example
Okay, let's go through this complex step-by-step program:
fn foo (x: & i32 ) {
let y = 10 ;
let z = & y;
baz (z);
bar (x, z);
}
fn bar (a: & i32 , b: & i32 ) {
let c = 5 ;
let d = Box :: new ( 5 );
let e = & d;
baz (e);
}
fn baz (f: & i32 ) {
let g = 100 ;
}
fn main () {
let h = 3 ;
let i = Box :: new ( 20 );
let j = & h;
foo (j);
}
First, we call main () :
AddressName Value
2 30 twenty
... ... ...
two j 0
one i 2 30
0 h 3
We allocate memory for j , i , and h . i is on the mound, that is why its
value points towards it.
Then at the end of main () , foo () is called:
AddressName Value
2 30 twenty
... ... ...
5 z 4
4 and 10
3 x 0
two j 0
one i 2 30
0 h 3
Space is allocated for x , y , and z . The argument x has the same value as j
, because that is what we provided to the function. It is a pointer to address 0
, since j points to h .
Then foo () calls baz () , passing it z :
AddressName Value
2 30 twenty
... ... ...
7 g 100
6 F 4
5 z 4
4 and 10
3 x 0
two j 0
one i 2 30
0 h 3
We have allocated memory for f and g . baz () is very short, so when it ends,
we get rid of your activation record:
AddressName Value
2 30 twenty
... ... ...
5 z 4
4 and 10
3 x 0
two j 0
one i 2 30
0 h 3
Then foo () calls bar () with x and z :
AddressName Value
2 30 twenty
(2 30 ) - 1 5
... ... ...
10 and 9
9 d (2 30 ) - 1
8 c 5
7 b 4
6 to 0
5 z 4
4 and 10
3 x 0
two j 0
one i 2 30
0 h 3
We end up assigning another value on the mound, so we have to subtract one
from 2 . It is easier to write that than 1,073,741,823 . In any case, we set the
30
variables as usual.
At the end of bar () , this calls baz () :
AddressName Value
2 30 twenty
(2 30 ) - 1 5
... ... ...
12 g 100
eleven F 9
10 and 9
9 d (2 30 ) - 1
8 c 5
7 b 4
6 to 0
5 z 4
4 and 10
3 x 0
two j 0
one i 2 30
0 h 3
With this, we are at our deepest point! Wow! Congratulations on having
followed all this and having come so far.
Then baz () ends, we get rid of f and g :
Address Name Value
2 30 twenty
(2 30 ) - 1 5
... ...
...
10 and 9
9 d (2 30 ) - 1
8 c 5
7 b 4
6 to 0
5 z 4
4 and 10
3 x 0
two j 0
one i 2 30
0 h 3
Next, we return from bar () . d in this case is a Box <T> , so it also releases
what it points to: (2 ) - 1. 30
Then finally main () returns, which cleans up the rest. When i is released (via
Drop ) it will also clean the rest on the mound.
Tests
Testing programs can be an effective way to show the presence of bugs, but it
is hopelessly inadequate to show their absence. Edsger W. Dijkstra, "The
Humble Programmer" (1972)
We will talk about how to test Rust code. What we will not be talking about
is the correct way to test Rust code. There are many schools of thought
regarding the correct and incorrect way of writing tests. All these approaches
use the same basic tools, in this section we will show you the syntax to make
use of them.
running 1 test
test it_works ... ok
Doc-tests adder
running 0 tests
running 1 test
test it_works ... FAILED
failures:
failures:
it_works
running 1 test
test it_works ... ok
Doc-tests adder
running 0 tests
running 1 test
test it_works ... ok
Doc-tests adder
running 0 tests
#[test]
fn it_works () {
assert_eq! (4, sum_two (2));
}
This is a very common use of assert_eq! : call some function with some known
arguments and compare the output of that call with the expected output.
# [cfg (test)]
mod tests {
use super :: sum_two;
#[test]
fn it_works () {
assert_eq! (4, sum_two (2));
}
}
There are a few changes here. The first is the inclusion of a mod tests with a
cfg attribute . The module allows us to group all our tests, and also allows us
to define support functions if necessary, all that is not part of our crate. The
cfg attribute only compiles our test code if we were trying to run the tests.
This can save compilation time, it also ensures that our tests are completely
excluded from a normal compilation.
The second change is the use statement . Because we are in an internal
module, we need to make our test available within our current scope. This
can be annoying if you have a large module, and that is why the use of the
glob facility is common . Let's change our src / lib.rs to make use of it:
# [cfg (test)]
mod tests {
use super :: *;
#[test]
fn it_works () {
assert_eq! (4, sum_two (2));
}
}
Note the line use different. Now we run our tests:
$ test charge
Compiling adder v0. 1.0 (file: /// Users / goyox86 / Code / rust / adder)
Running target / debug / adder-ba17f4f6708ca3b9
running 1 test
test tests :: it_works ... ok
Doc-tests adder
running 0 tests
#[test]
fn it_works () {
assert_eq! (4, adder :: sum_two (2));
}
It looks similar to our previous tests, but slightly different. Now we have an
extern crate adder at the beginning. This is because the tests in the directory are a
separate crate, so we must import our library. This is also why tests is a great
place to write integration tests: these tests use the library just like any other
consumer would.
Let's run them:
$ test charge
Compiling adder v0. 1.0 (file: /// Users / goyox86 / Code / rust / adder)
Running target / debug / lib -f 71036151ee98b04
running 1 test
test it_works ... ok
running 1 test
test tests :: it_works ... ok
running 0 tests
Documentation tests
Nothing is better than documentation with examples. Nothing is worse than
examples that don't work, because the code has changed since the
documentation was written. Regarding this, Rust supports the automatic
execution of the examples present in your documentation. Here's a polished
src / lib.rs with examples:
//! The `adder` crate provides functions that add numbers to other numbers.
//!
//! # Examples
//!
//!
//! assert_eq! (4, adder :: add_two (2)); //! `` ''
/// This function adds two to its argument. /// /// # Examples /// /// /// use adder ::
add_two; /// /// assert_eq! (4, add_two (2)); /// pub fn add_two (a: i32) -> i32 {a + 2}
[cfg (test)]
mod tests {use super :: *;
#[test]
fn it_works () {
assert_eq! (4, add_two (2));
}
}
Note the module-level documentation with `//!` And the function-level documentation with `///`. Rust
documentation supports Markdown in comments and treble (\ `\` \ `) delimit code blocks. It is
conventional to include the `# Examples` section, exactly like this, followed by the examples.
bash
$ test charge
Compiling adder v0.1.0 (file: /// Users / goyox86 / Code / rust / adder)
Running target / debug / lib-f71036151ee98b04
running 1 test
test it_works ... ok
running 1 test
test tests :: it_works ... ok
Doc-tests adder
running 2 tests
test _0 ... ok
test sum_two_0 ... ok
Conditional Compilation
Rust has a special attribute, # [cfg] , that allows you to compile code based on
an option provided to the compiler. It has two forms:
# [cfg (foo)]
# fn foo () {}
# [cfg (bar = "baz")]
# fn bar () {}
It also has some helpers:
# [cfg (any (unix, windows))]
# fn foo () {}
# [cfg (all (unix, target_pointer_width = "32"))]
# fn bar () {}
Documentation
Documentation is an important part of any software project and a first-class
citizen at Rust. Let's talk about the tools that Rust provides to document your
projects.
About rustdoc
The Rust distribution includes a tool, rustdoc , in charge of generating the
documentation. rustdoc is also used by Cargo through cargo doc .
Documentation can be generated in two ways: from source code, or from
Markdown files.
Documenting source code
The main way to document a Rust project is through the annotation of the
source code. For this purpose, you can use documentation comments:
/// Build a new `Rc`.
///
/// # Examples
///
///
/// use std :: rc :: Rc; /// /// let five = Rc :: new (5); /// `` `pub fn new
(value: T) -> Rc {// the implementation goes here}
The code above generates documentation that looks like [this] [rc-new] (English). I've left the
implementation out, with a regular comment instead. That's the first thing to note about this annotation:
use `///`, instead of `//`. The triple slash indicates that it is a documentation comment.
`` 'rust
/// The type `Option`. See [module level documentation] (../) for more
information.
enum Option <T> {
/// No value
None
/// Some `T` value
Some (T),
}
The above works, but this does not:
/// The type `Option`. See [module level documentation] (../) for more
information.
enum Option {
None, /// No value
Some (T), /// Some value `T`
}
You will get an error:
hello.rs:4:1: 4: 2 error: expected ident, found `}`
hello.rs:4}
^
Special sections
/// # Examples
# fn foo () {}
Below are the special sections. These are indicated with a header, # . There
are three types of header that are commonly used. These are not special
syntax, just convention, for now.
/// # Panics
# fn foo () {}
Bad and unrecoverable uses of a function (eg programming errors) in Rust
are usually indicated by panics, which at least kill the current thread. If your
role has a non-trivial contract like this, which is panicked / enforced,
documenting it is very important.
/// # Failures
# fn foo () {}
If your function or method returns a Result <T, E> , then describing the
conditions under which Err (E) returns is a good thing to do. This is slightly
less important than Panics , as it is encoded in the type system, but it is still
something to do.
/// # Safety
# fn foo () {}
If your function is unsafe (insecure), you should explain what are the
invariants that must be maintained by the caller.
/// # Examples
///
///
/// use std :: rc :: Rc; /// /// let five = Rc :: new (5); /// `` `
fn foo () {}
Third, `Examples`, include one or more examples of the use of your function or method, and your users
will love you. These examples go inside code block annotations, which we will talk about in a moment,
they can have more than one section:
`` 'rust
/// # Examples
///
/// Simple `& str` patterns:
///
///
/// let v: Vec <& str> = "Mary had a little lamb" .split ('') .collect (); ///
assert_eq! (v, vec! ["Mary", "had", "a", "little lamb"]); /// /// /// More complex
patterns with lambdas: /// /// /// let v: Vec <& str> = "abc1def2ghi" .split (| c: char |
c.is_numeric ()). collect (); /// assert_eq! (v, vec! ["abc", "def", "ghi"]); /// `` `
fn foo () {}
Let's discuss the details of those code blocks.
`` 'rust
///
/// println! ("Hello, world"); /// `` `
fn foo () {}
If you want code other than Rust, you can add an annotation:
`` 'rust
/// `` `c
/// printf ("Hello, world \ n");
///
fn foo () {}
The syntax of this section will be highlighted according to the language you are showing. If you are
only displaying plain text, use `text`.
Here, it is important to choose the correct annotation, because `rustdoc` uses it in an interesting way: It
can be used to test your examples, so that they do not become obsolete over time. If you have any C
code but `rustdoc` thinks it's Rust, it's because you forgot the annotation,` rustdoc` complained when
trying to generate the documentation.
## Documentation as evidence
Let's discuss our sample documentation:
`` 'rust
///
/// println! ("Hello, world"); /// `` `
fn foo () {}
You'll notice that you don't need a `fn main ()` or something else. `rustdoc` will automatically add a
main () around your code, and in the right place. For example:
`` 'rust
///
/// use std :: rc :: Rc; /// /// let five = Rc :: new (5); /// `` `
fn foo () {}
It will become the test:
`` 'rust
fn main () {
use std :: rc :: Rc;
let five = Rc :: new (5);
}
Here is the complete algorithm that rustdoc uses to post-process the examples:
1. Any leftover #! [Foo] attributes are left intact as a crate attribute.
2. Some common attributes are inserted, including unused_variables ,
unused_assignments , unused_mut , unused_attributes , and dead_code . Small
examples occasionally trigger these lints.
3. If the example does not contain extern crate , then the extern crate
<micrate>; is inserted.
4. Finally, if the example does not contain fn main , the text is wrapped in fn
main () {your_code}
Sometimes this is not enough. For example, all these code examples with ///
that we've been talking about? Plain text:
/// Some documentation.
# fn foo () {}
Looks different on departure:
/// Some documentation.
# fn foo () {}
Yes, it is correct: you can add lines that start with # , and these will be
removed from the output, but they will be used in the compilation of your
code. You can use this to your advantage. In this case, the documentation
comments need to apply to some kind of function, so if I want to show just
one documentation comment, I need to add a little function definition below.
At the same time, it is there just to satisfy the compiler, so hiding it makes the
example cleaner. You can use this technique to explain longer examples in
detail, while preserving your documentation's ability to be tested. For
example, this code:
let x = 5 ;
let y = 6 ;
println! ( "{}" , x + y);
Here's an explanation, rendered:
First, we assign x the value of five:
let x = 5 ;
# let y = 6 ;
# println! ( "{}" , x + y);
Next, we assign six to y :
# let x = 5 ;
let y = 6 ;
# println! ( "{}" , x + y);
Finally, we print the sum of x and y :
# let x = 5 ;
# let y = 6 ;
println! ( "{}" , x + y);
Here's the same explanation, in plain text:
First, we assign x the value of five:
let x = 5;
# let y = 6;
# println! ("{}", x + y);
Next, we assign six to y :
# let x = 5;
let y = 6;
# println! ("{}", x + y);
Finally, we print the sum of x and y :
# let x = 5;
# let y = 6;
println! ("{}", x + y);
By repeating all parts of the example, you can ensure that your example still
compiles, showing only the parts relevant to your explanation.
Documenting macros
Here is an example of documentation to a macro:
/// Panic with a provided message unless the expression is evaluated to true.
///
/// # Examples
///
///
/// # # [macro_use] extern crate foo; /// # fn main () {/// panic_unless! (1 + 1
== 2, "The mathematics is broken."); /// #} /// /// /// should_panic /// # #
[macro_use] extern crate foo; /// # fn main () {/// panic_unless! (true == false,
"I am broken."); /// #} /// `` `
[macro_export]
macro_rules! panic_unless {($ condition: expr, $ ($ rest: expr), +) => ({if! $
condition {panic! ($ ($ rest), +);}}); }
fn main () {}
You will notice three things: we need to add our own `extern crate` line, so that we can add the` #
[macro_use] `attribute. Second, we will need to add our own `main ()`. Finally, a judicious use of `#` to
comment on those two things, so that they are shown in the output.
bash
$ rustdoc --test path / to / my / crate / root.rs
# or
$ test charge
Correct, charge test tests embedded documentation as well. However, I load test
, it won't test binary crates, just libraries. This is because of the way rustdoc
works: it links to the library to be tested, but in the case of a binary, there is
nothing to link to.
There are a few more annotations that are useful to help rustdoc do the right
thing when testing your code:
/// `` `ignore
/// fn foo () {
///
fn foo () {}
The `ignore` directive tells Rust to ignore the code. This is the form that you will almost never want, as
it is the most generic. Instead, consider scoring with `text` if it is not code, or use` # `s to get a working
example that only shows the part that interests you.
`` 'rust
/// `` `should_panic
/// assert! (false);
///
fn foo () {}
`should_panic` tells` rustdoc` that the code must compile correctly, but without the need to successfully
pass a test.
`` 'rust
/// `` `no_run
/// loop {
/// println! ("Hello, world");
///}
///
fn foo () {}
The `no_run` attribute will compile your code, but will not execute it. This is important for examples
like "Here's how to start a network service," which you should make sure compiles, but it could cause
an infinite loop!
`` 'rust
mod foo {
//! This is documentation for the `foo` module.
//!
//! # Examples
// ...
}
This is where you will see //! most often used: for module documentation. If
you have a module in foo.rs , frequently when you open its code you will see
this:
//! A module to use `foo`s.
//!
//! The `foo` module contains a lot of functionality blah blah blah
Documentation feedback style
Other documentation
All of this behavior works on non-Rust files as well. Because comments are
written in Markdown, they are often .md files .
When you write documentation to Markdown files, you don't need to prefix
the documentation with comments. For example:
/// # Examples
///
///
/// use std :: rc :: Rc; /// /// let five = Rc :: new (5); /// `` `
fn foo () {}
it's just
~~~ markdown
# Examples
use std :: rc :: Rc;
let five = Rc :: new (5);
~~~
when it is in a Markdown file. There is only one detail, markdown files need to have a title like this:
markdown
% Title
doc attributes
At a deeper level, documentation comments are another way to write
documentation attributes:
/// Este
# fn foo () {}
# [doc = "this"]
# fn bar () {}
they are the same as these:
//! Este
# [doc (no_inline)]
pub use foo :: bar;
Controlling HTML
You can control some aspects of the HTML that rustdoc generates through the
#! [Doc] version of the attribute:
Safety note
Markdown in documentation comments is set raw on the final page. Be
careful with literal HTML:
/// <script> alert (document.cookie) </script>
# fn foo () {}
Iterators
Let's talk about cycles.
Remember the Rust for cycle ? Here is an example:
for x in 0..10 {
println! ("{}", x);
}
Now that you know more about Rust, we can talk in detail about how this
code works. The ranges ( 0..10 ) are iterators. An iterator is something we can
call the .next () method on repeatedly, and the iterator provides us with a
sequence of elements.
For example:
let mut range = 0..10;
loop {
match rank.next () {
Some (x) => {
println! ("{}", x);
},
None => {break}
}
}
We create a link (a variable) to the range, our iterator. Then we iterate
through the loop cycle , with an internal match . This match uses the result of
rank.next () , which gives us a reference to the next value in the iterator. next
returns an Option <i32> , in this case, which will be Some (i32) when we have a
value and None when we run out of values. If we get Some (i32) , we print it,
and if we get None , we break the loop, leaving it through break .
This code example is basically the same as our version of a for for loop . The
for loop is just a practical way to write a loop / match / break construct .
However, for loops are not the only thing that uses iterators. Write your own
iterator involves implementing the Trait Iterator . While doing so is outside
the scope of this guide, Rust provides a number of useful iterators to
accomplish various tasks. Before talking about that, we should talk about an
anti-patron. Said anti-pattern is to use ranges in the manner outlined above.
Yes, we just talked about how cool the ranges are. But they are also very
primitive. For example, if we need to iterate through the content of a vector,
we might be tempted to write something like this:
let nums = vec! [1, 2, 3];
for i in 0..nums.len () {
println! ("{}", nums [i]);
}
This is not strictly worse than using an iterator. You can iterate into vectors
directly, write this:
let nums = vec! [1, 2, 3];
Let's talk about consumers first, because we've already seen an iterator, the
ranges.
Consumers
A consumer operates on an iterator, returning some kind of value or values.
The most common consumer is collect () . This code does not compile, but
shows the intention:
let uno_hasta_cien = (1..101) .collect ();
As you can see we call collect () on the iterator. collect () takes as many values
as the iterator provides, returning a collection of results. So why doesn't this
code compile? Rust can't determine what kinds of things you want to collect,
and that's why you need to let him know. This is the version that compiles:
let uno_hasta_cien = (1..101) .collect :: <Vec <i32>> ();
If you remember, the syntax :: <> allows you to give a hint about the type, in
our case we are saying that we want a vector of integers. It is not always
necessary to use the full type. _ will allow you to give a partial hint about the
type:
let one_up to one hundred = (1..101) .collect :: <Vec <_>> ();
This says "Collect in a Vec <T> , please, but infer that it's T for me.". _ is for
this reason sometimes called a "type placeholder".
collect () is the most common consumer, but there are others. find () is one of
them:
let mayor_a_cuarenta_y_dos = (0..100)
.find (| x | * x> 42);
match mayor_a_cuarenta_y_dos {
Some (_) => println! ("We have some numbers!"),
None => println! ("No numbers were found :("),
}
find receives a closure, and works on a reference to each element of an
iterator. Said closure returns true if the element is the one we are looking for
and false otherwise. Because we might not find an element that meets our
criteria, find returns an Option instead of an element.
Another major consumer is fold . It looks like this:
let sum = (1..4) .fold (0, | sum, x | sum + x);
. It takes two arguments: the first is an element
fold (base, | accumulator, element | ...)
called base . The second is a closure which in turn takes two arguments: the
first is called the accumulator , and the second is an element . In each
iteration, the closure is called, and the result is used as the accumulator value
in the next iteration. In the first iteration, the base is the accumulator value.
Well that's a little confusing. Let's examine the values of all things in this
iterator:
baseaccumulatorelementclosure result
0 0 one one
0 one two 3
0 3 3 6
We have called fold () with these arguments:
# (1..4)
.fold (0, | sum, x | sum + x);
So 0 is our base, sum is our accumulator, and x is our element. In the first
iteration, we assign sum to 0 and x is the first element in our range, 1 . Then
we add sum and x , which gives us 0 + 1 = 1 . In the second iteration, that
value becomes the value of our accumulator, sum , and the element is the
second element in the range, 2 . 1 + 2 = 3 and likewise becomes the
accumulator value for the last iteration. In that iteration, x is the last element,
3 , and 3 + 3 = 6 , the final result for our sum . 1 + 2 + 3 = 6 , that's the result we
get.
Whew. Fold may be a little strange at first glance, but once it clicks, you can
use it everywhere. Whenever you have a list of things, and need a single
result, fold is appropriate.
Consumers are important because of an additional property of iterators that
we haven't talked about yet: laziness. Let's talk more about iterators and
you'll see why consumers are important.
Iterators
As we've said before, an iterator is something we can call the .next () method
on repeatedly, and it returns a sequence of elements. Because we need to call
the method, the iterators can be lazy and not generate all the values in
advance. This code, for example, does not generate numbers 1-99 . Instead it
creates a value that represents the sequence:
let nums = 1..100;
Because we did nothing with the range, it did not generate the sequence. Let's
add a consumer:
let nums = (1..100) .collect :: <Vec <i32>> ();
Now collect () will require the range to supply some numbers, and therefore
will have to do the work of generating the sequence.
Ranges are one of the two basic forms of iterators that you will see. The other
is iter () . iter () can transform a vector into a simple iterator that provides one
element at a time:
let nums = vec! [1, 2, 3];
for num in nums.iter () {
println! ("{}", num);
}
These two basic iterators should be useful. There are more advanced iterators,
including those that are infinite.
Enough about iterators, Iterator Adapters are the last concept related to
Iterators that we should mention. Let's do it!
Iterator adapters
The adapters iterators take an iterator and modify it in some way, producing
a new one. The simplest is called map :
(1..100) .map (| x | x + 1);
map is called in another iterator, map produces a new iterator in which each
reference to an element has the closure that has been provided as an
argument. The previous code will give us the numbers 2-100 , well, almost! If
you compile the example, you will get a warning:
warning: unused result which must be used: iterator adapters are lazy
and
do nothing unless consumed, # [warn (unused_must_use)] on by
default
(1..100) .map (| x | x + 1);
^ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Laziness strikes again! That closure will never run. This example does not
print any number:
(1..100) .map (| x | println! ("{}", X));
If you are trying to run a closure in an iterator to get its side effects use a for .
for i in (1 ..). take (5) {
println! ("{}", i);
}
This will print:
one
two
3
4
5
is an adapter that takes a closure as an argument. This closure returns
filter ()
true or false . The new iterator that filter () produces only elements for which
the closure returns true :
for i in (1..100) .filter (| & x | x% 2 == 0) {
println! ("{}", i);
}
This will print all even numbers between one and one hundred. (Note that
because filter does not consume the elements being iterated, a reference to
each element is passed to it, therefore, the predicate uses the & x pattern to
extract the integer.)
(one..)
.filter (| & x | x% 2 == 0)
.filter (| & x | x% 3 == 0)
.take (5)
.collect :: <Vec <i32>> ();
The above gives you a vector containing 6 , 12 , 18 , 24 , and 30 .
This is a small sample of the things that iterators, iterator adapters, and
consumers can help you with. There are a variety of really useful iterators,
along with the fact that you can write your own. Iterators provide a safe and
efficient way to manipulate all kinds of lists. They are a bit unusual at first
glance, but if you play around with them a bit, you will be hooked
Concurrence
Concurrency and parallelism are two incredibly important topics in computer
science, they are also a hot topic in the industry today. Computers
increasingly have more and more cores, even so, still some developers are not
ready to fully use us.
Rust's memory management security also applies to its concurrency history.
Even concurrent programs must be memory-safe, with no race conditions.
Rust's type system is up to the challenge, and gives you powerful avenues to
reason about concurrent code at compile time.
Before we talk about the concurrency features that come with Rust, it's
important to understand something: Rust is low-level, low enough to the
point that all of these facilities are implemented in the standard library. This
means that if you don't like some aspect of the way Rust handles
concurrency, you can implement an alternative way of doing it. Mine is a
vivid example of this principle in action.
Threads
The standard Rust library provides a library for thread handling, which
allows you to run rust code in parallel. Here is a basic example of using std ::
thread :
fn main () {
thread :: spawn (|| {
println! ( "Hello from a thread!" );
});
}
The thread :: spawn () method accepts a closure as an argument, a closure that is
executed on a new thread. thread :: spawn () returns a handle to the new thread,
which can be used to wait for the thread to finish and then extract its result:
fn main () {
let handle = thread :: spawn (|| {
"Hello from a thread!"
});
println! ( "{}" , handle.join (). unwrap ());
}
Many languages possess the ability to run threads, but it is wildly insecure.
There are whole books on how to prevent mistakes that occur as a result of
sharing mutable state. Rust helps with its rate system, preventing race
conditions at compile time. Let's talk about how you can effectively share
things between threads.
Mutable State Shared Safe
Due to Rust's type system, we have a concept that sounds like a lie: "safe
shared mutable state". Many developers agree that the shared mutable state is
very, very bad.
Someone once said:
The shared mutable state is the root of all evil. Most languages try to deal
with this problem through the 'mutable' part, but Rust confronts it by solving
the 'shared' part.
The same membership that prevents improper use of pointers also helps
eliminate race conditions, one of the worst concurrency-related bugs.
As an example, a Rust program that would have a career condition in many
languages. In Rust I would not compile:
use std :: thread;
fn main () {
let mut data = vec! [1u32, 2, 3];
for i in 0..3 {
thread :: spawn (move || {
data [i] + = 1;
});
}
fn main () {
let mut data = Mutex :: new (vec! [1u32, 2, 3]);
for i in 0..3 {
let data = data.lock (). unwrap ();
thread :: spawn (move || {
data [i] + = 1;
});
}
fn main () {
let data = Arc :: new (Mutex :: new ( vec! [ 1u32 , 2 , 3 ]));
for i in 0 .. 3 {
let data = data.clone ();
thread :: spawn (move || {
let mut data = data.lock (). unwrap ();
data [i] + = 1 ;
});
}
thread :: sleep_ms ( 50 );
}
Now we call clone () on our Arc , which increments the internal counter. This
handle is then moved into the new thread. Let's examine the thread body
more closely:
# use std :: sync :: {Arc, Mutex};
# use std :: thread;
# fn main () {
# let data = Arc :: new (Mutex :: new ( vec! [ 1u32 , 2 , 3 ]));
# for i in 0 .. 3 {
# let data = data.clone ();
thread :: spawn (move || {
let mut data = data.lock (). unwrap ();
data [i] + = 1 ;
});
#}
# thread :: sleep_ms ( 50 );
#}
First, we call lock () , which gets the mutual exclusion lock. Because this
operation may fail, this method returns a Result <T, E> , and because this is just
an example, we do unwrap () on it to get a reference to the data. Real code
would have more robust error handling here. We are free to mutate it, since
we have the blockade.
Lastly, while the threads run, we wait for the culmination of a short timer.
This is not ideal: we could have chosen a reasonable time to wait, but we will
most likely wait longer than necessary, or not long enough, depending on
how long the threads take to finish computing when the program runs.
A more precise alternative to the timer would be to use one of the
mechanisms provided by the Rust standard library for inter-thread
synchronization. Let's talk about them: the channels.
Channels
Here is a version of our code that uses channels for synchronization, instead
of waiting for a specific time:
fn main () {
let data = Arc :: new (Mutex :: new ( 0u32 ));
for _ in 0 .. 10 {
let (data, tx) = (data.clone (), tx.clone ());
tx.send (());
});
}
for _ in 0 .. 10 {
rx.recv ();
}
}
We use the mpsc :: channel () method to build a new channel. We send (via send
) a single () through the channel, and then wait for the return of ten of them.
While this channel is only sending a generic signal, we can send any data that
is Send through the channel!
use std :: thread;
use std :: sync :: mpsc;
fn main () {
let (tx, rx) = mpsc :: channel ();
for _ in 0 .. 10 {
let tx = tx.clone ();
tx.send (response);
});
}
Panics
A panic! will cause an abrupt termination of the current thread. You can use
Rust threads as a simple isolation mechanism:
use std :: thread;
Error Handling
The plans best established by mice and men often go awry. "Tae a Moose",
Robert Burns
Sometimes things just go wrong. It is important to have a plan for when the
inevitable happens. Rust has rich support for handling errors that could (let's
be honest: will occur) occur in your programs.
There are two types of errors that can occur in your programs: failure and
panic. We will talk about the differences between the two, and then we will
discuss how to handle each one. Then we will discuss how to promote panic
flaws.
# let x = 5 ;
assert! (x == 5 );
We use assert! to declare that something is true (true). If the statement is not
true, then something is very wrong. Bad enough that you cannot continue
running in the current state. Another example is using the unreachable! () Macro
:
enum Event {
New Launch,
}
fn main () {
println! ("{}", descriptive_probability (NewLaunch));
}
The above will result in an error:
error: non-exhaustive patterns: `_` not covered [E0004]
While we know we have covered every possible case, Rust may not know.
You don't know what the probability is between 0.0 and 1.0. That is why we
add another case:
enum Event {
New Launch,
}
fn main () {
println! ( "{}" , descriptive_probability (NewLaunch));
}
We should never reach case _ , because of this we use the macro to indicate
it. unreachable! () produces a different type of error than Result . Rust calls those
kinds of mistakes panic .
# [derive (Debug)]
enum ErrorParseo {InvalidHeadLength, Invalid Version}
fn main () {
let version = parsear_version (& [ 1 , 2 , 3 , 4 ]);
match version {
Ok (v) => {
println! ( "working with version: {:?}" , v);
}
Err (e) => {
println! ( "head parsing error: {:?}" , e);
}
}
}
This function makes use of an enum, ErrorParseo , to list the errors that can
occur.
The Debug trait is the one that allows us to print the enum value using the {:?}
Format operation .
Using try!
When we write code that calls many functions that return the Result type ,
error handling can be tedious. The try macro ! It hides some of the repetitive
code for error propagation in the call stack.
try! replaces:
struct info {
name: String ,
age: i32 ,
grade: i32 ,
}
if let Err (e) = writeln! (& mut file, "name: {}" , info.name) {
return Err (e)
}
if let Err (e) = writeln! (& mut file, "age: {}" , info.edad) {
return Err (e)
}
if let Err (e) = writeln! (& mut file, "grade: {}" , info.rgrado) {
return Err (e)
}
return Ok (());
}
With:
use std :: fs :: File;
use std :: io;
use std :: io :: prelude :: *;
struct info {
name: String ,
age: i32 ,
grade: i32 ,
}
return Ok (());
}
Introduction
This guide will use the snappy compression / decompression library as an
introduction to writing bindings to external code. Rust currently cannot call
code in a C ++ library directly, but snappy includes a C interface
(documented in snappy-ch ).
The following is a handy example of how to call a foreign function that will
compile assuming snappy is installed:
# #! [feature (libc)]
extern crate libc;
use libc :: size_t;
fn main () {
let x = unsafe {snappy_max_compressed_length (100)};
println! ("maximum length of a 100 bytes compressed buffer: {}", x);
}
The extern block is a list of function signatures in a foreign library, in this
case with the platform's C application binary interface (ABI). The # [link (...)]
attribute is used to instruct the linker to link to the snappy library so that
symbols can be resolved.
It is assumed that the interfaces to foreign functions are insecure, that is why
the calls to them must be inside an unsafe {} block as a promise to the
compiler that everything contained in it is really safe. Libraries in C
sometimes expose interfaces that are not thread-safe, and almost any function
that takes a pointer as an argument is not valid for all possible inputs because
the pointer could be a hanging pointer, and flat pointers are left behind.
outside of Rust's secure memory model.
When declaring the argument types of a foreign function, the Rust compiler
cannot check whether the declaration is correct, so specifying it correctly is
part of maintaining the correct binding at runtime.
The extern block can be extended to cover the full snappy API:
# #! [feature (libc)]
extern crate libc;
use libc :: {c_int, size_t};
# #! [feature (libc)]
# extern crate libc;
# use libc :: {c_int, size_t};
# unsafe fn snappy_validate_compressed_buffer (_: * const u8 , _: size_t)
-> c_int { 0 }
# fn main () {}
pub fn validate_buffer_compressed (src: & [ u8 ]) -> bool {
unsafe {
snappy_validate_compressed_buffer (src.as_ptr (), src.len () as
size_t) == 0
}
}
The compressed_buffer_buffer wrapper function makes use of an unsafe block , but
makes sure that calling it is safe for all inputs by excluding the unsafe from
the function signature.
The snappy_compress and snappy_uncompress functions are more complex, because
a buffer has to be assigned to maintain the output.
The snappy_max_compressed_length function can be used to assign a vector with
the maximum capacity required to store the compressed output. The vector
can then be passed to the snappy_compress function as an output parameter. An
output parameter is also passed to get the actual length after compression to
assign the length.
# #! [feature (libc)]
# extern crate libc;
# use libc :: {size_t, c_int};
# unsafe fn snappy_compress (a: * const u8 , b: size_t, c: * mut u8 ,
# d: * mut size_t) -> c_int { 0 }
# unsafe fn snappy_max_compressed_length (a: size_t) -> size_t {a}
# fn main () {}
pub fn compress (orig: & [ u8 ]) -> Vec < u8 > {
unsafe {
let long_orig = orig.len () as size_t;
let porig = orig.as_ptr ();
# #! [feature (libc)]
# extern crate libc;
# use libc :: {size_t, c_int};
# unsafe fn snappy_uncompress (compressed: * const u8 ,
# compressed_length: size_t,
# uncompressed: * mut u8 ,
# uncompressed_length: * mut size_t) -> c_int { 0 }
# unsafe fn snappy_uncompressed_length (compressed: * const u8 ,
# compressed_length: size_t,
# result: * mut size_t) -> c_int { 0 }
# fn main () {}
pub fn unzip (orig: & [ u8 ]) -> Option < Vec < u8 >> {
unsafe {
let long_orig = orig.len () as size_t;
let porig = orig.as_ptr ();
Destroyers
External libraries usually transfer the membership of the resources to the
calling code. When this occurs, we must use the Rust destroyers to provide
security and the guarantee of the release of those resources (especially in the
case of a panic).
fn main () {
unsafe {
register_callback (callback);
shoot_callback (); // Trigger the callback
}
}
Code C:
typedef void (* callback_rust) (int32_t) ;
callback_rust cb;
void shoot_callback () {
cb ( 7 ); // Call callback (7) in Rust
}
In this example Rust's main () will call trigger_callback () in C, which in turn will
callbackback () in Rust.
fn main () {
// Creating the object that will be referenced in the callback
let mut object_rust = Box :: new (ObjectRust {a: 5});
unsafe {
register_callback (& mut * object_rust, callback);
shoot_callback ();
}
}
Code C:
typedef void (* callback_rust) ( void *, int32_t) ;
void * target_cb;
callback_rust cb;
void shoot_callback () {
cb (cb_target, 7 ); // I will call callback (& RustObject, 7) in Rust
}
Asynchronous callbacks
In the examples above, callbacks are invoked as a direct reaction to a function
call to the external library in C. Control over the current thread is changed
from Rust to C for callback execution, but in the end the callback is executed
in the same thread that called the function that triggered the callback.
Things get more complicated when the external library creates its own
threads and invokes callbacks from them. In these cases access to Rust data
structures within callbacks is especially insecure and appropriate
synchronization mechanisms must be used. In addition to classic
synchronization mechanisms such as mutexes, one possibility in Rust is the
use of channels (in std :: sync :: mpsc ) to transfer data from the C thread that
invokes the callback to a Rust thread.
If an asynchronous callback targets a special object in the Rust address space
it is also absolutely necessary that no other callbacks be executed by the C
library after the respective Rust object has been destroyed. This can be done
by de-registering the callback in the object's destructor and designing the
library in a way that guarantees that no callback will be executed after the de-
registration.
Link
The link attribute in extern blocks provides the basic building block to instruct
rustc how to link to native libraries. Today there are two forms of the link
attribute:
# [link (name = "foo")]
# [link (name = "foo", kind = "bar")]
In both cases, foo is the name of the native library that we are linking to, and
in the second case, bar is the type of native library that the compiler is linking
to. There are currently three types of known native libraries:
Dynamics - # [link (name = "readline")]
Statics - # [link (name = "my_build_dependency", kind = "static")]
Frameworks - # [link (name = "CoreFoundation", kind = "framework")]
Regardless of the type of output for the crate, the static native library will be
included in the output, meaning that distribution of the native static library is
not necessary.
A normal dynamic dependency: Common system libraries (such as readline ) are
available on a large number of systems, and often a static copy of those libraries may not
exist. When this dependency is included in a Rust crate, partial targets (like rlibs) will not
link to the library, but when the rlib is included in a final target (like a binary), the library
will be linked.
Unsafe blocks
Some operations, such as referencing flat pointers or calls to functions that
have been marked as unsafe are only allowed within unsafe blocks. The
unsafe blocks isolate the insecurity and are a promise to the compiler that the
insecurity will not leak out of the block.
Unsafe functions, on the other hand, advertise insecurity to the outside world.
An insecure function is written as follows:
unsafe fn kaboom (ptr: * const i32 ) -> i32 {*} ptr
This function can be invoked only from an unsafe block or another unsafe
function .
Accessing external globals
Foreign APIs sometimes export a global variable that could do something
like keep track of some global state. In order to access these variables, you
must declare them in external blocks with the static keyword :
# #! [feature (libc)]
extern crate libc;
fn main () {
println! ("You have version {} of readline.",
rl_readline_version as i32);
}
Alternatively, you may need to alter the global state provided by a foreign
interface. To do this, statics can be declared with mut in order to mutate
them.
# #! [feature (libc)]
extern crate libc;
fn main () {
let prompt = CString :: new ("[my-awesome-shell] $"). unwrap ();
unsafe {
rl_prompt = prompt.as_ptr ();
Most abys are self-explanatory, but the abi system may seem a little weird.
This constraint selects whatever the appropriate ABI is to interoperate with
the target libraries. For example, in win32 with an x86 architecture, it means
that the abi used will be stdcall . In x86_64, however, Windows uses the
convention called C , then used C . This translates to that in our previous
example, we could have used extern "system" {...} to define a block for all
Windows systems, not just x86.
Interoperability with external code
Rust ensures that the distribution of a struct is compatible with the
representation of the platform in C only if the attribute # [repr (C)] is applied. #
[repr (C, packed)] can be used to distribute members without padding. # [repr (C)]
can also be applied to an enum.
Rust boxes ( Box <T> ) use non-nullable pointers as handles that point to the
contained object. However, they should not be created manually because they
are handled by internal dispatchers. References can be safely assumed as
direct non-nullable pointers to the type. However, breaking the borrowing
check or the mutability rules is not guaranteed to be safe, which is why the
use of flat pointers ( * ) is preferred if necessary because the compiler cannot
assume many things about from them.
Vectors and character strings share the same in-memory layout, and utilities
for interacting with C APIs are available in the vec and str modules .
However, character strings are not terminated at \ 0 . If you need a character
string ending in NUL to interact with C, then you must make use of the
CString type in the std :: ffi module .
The standard library includes type aliases and function definitions for the C
standard library in the libc module , and Rust defaults to libc and libm .
The "Null Pointer Optimization"
Certain types are defined not to be null . This includes references ( & T , &
mut T ), boxes ( Box <T> ), and function pointers ( extern "abi" fn () ). When you
interface with C, pointers that can be null are sometimes used. As a special
case, a generic enum containing exactly two variants, of which one does not
contain data and the other contains a single field, is eligible for "null pointer
optimization". When said enum is instantiated with one of the non-nullable
types, it is represented as a single pointer, and the variant without data is
represented as the null pointer. So Option <extern "C" fn (c_int) -> c_int> is how one
represents a nullable function pointer using ABI of C.
Calling coding Rust from C
You may want to compile Rust code so that it can be called from C. This is
easy, but it requires certain things:
# [no_mangle]
pub extern fn hello_rust () -> * const u8 {
"Hello world! \ 0" .as_ptr ()
}
# fn main () {}
The extern makes this function adhere to the C calling convention, as
discussed in " Foreign calling conventions ". The no_mangle attribute disables
Rust's name mangling, making it easier to bind.
FFI and panics
It is important to be aware of panic! os when working with FFI. A panic! at the
limit of FFI is undefined behavior. If you are writing code that can panic, you
must run it in another thread, so the panic will not spread to C:
use std :: thread;
# [no_mangle]
pub extern fn oh_no () -> i32 {
let h = thread :: spawn (|| {
panic! ( "Oops!" );
});
match h.join () {
Ok (_) => 1 ,
Err (_) => 0 ,
}
}
# fn main () {}
This is because the standard library has a Borrow <str> for String impl .
For most types, when you want to take a type either owned or borrowed, an
& T is enough. But one area in which Borrow is effective is when there is
more than one type of borrowed security. This is especially true of references
and slices: you can both have a & T one & mut T . If we want to accept both
types, Borrow is what we need:
let mut i = 5 ;
AsRef
The AsRef trait is a conversion trait. It is used to convert some value to a
reference in generic code. Like this:
let s = "Hello" .to_string ();
Distribution channels
The Rust project uses a concept called 'distribution channels' to manage
releases.
It is important to understand this process so that you can decide which
version of Rust your project should use.
Overview
There are three channels for Rust releases:
Nocturnal (nightly)
Beta
Stable
Nightly releases are created once a day. Every six weeks, the latest nightly
release is promoted to 'Beta'. At this point, you will only receive patches that
fix serious bugs. Six weeks later the beta is promoted to 'Stable', and it
becomes the new 1.x release .
This process occurs in parallel. Every six weeks, on the same day the nocturn
goes up to beta, the beta is promoted to stable. When 1.x is released, at the
same time 1. (x + 1) -beta is released, and the night becomes the first version of
1. (x + 2) -nightly .
Choosing a version
Generally speaking, unless you have a specific reason, you should use the
stable distribution channel. Those releases are intended for a general
audience.
However, depending on your interest in Rust, you could choose the night.
The balance is as follows: on the night channel, you can make use of new
Rust features. However, unstable features are subject to change, which is why
any nightclub has the ability to break your code. If you use the stable release,
you don't have access to experimental features, but the next release of Rust
won't cause significant problems because of changes.
Chapter IV
Syntax and Semantics
This section breaks Rust into small pieces, one for each concept.
If you want to learn Rust from the bottom up, reading this section in order is
a very good way to do it.
These sections form a reference for each concept, if you are reading another
tutorial and you find something confusing, you can find it explained
somewhere in this section.
Variable Links
Virtually any non-'Hello World 'program uses variable links . These links to
variables look like this:
fn main () {
let x = 5 ;
}
Placing fn main () { in each example is a bit tedious, so in the future we will
skip it. If you are following step by step, be sure to edit your main () function ,
instead of leaving it out. Otherwise you will get an error.
In many languages, this is called a variable , but Rust's variable bindings
have a few tricks up their sleeve. For example the left side of a let expression
is a ' pattern ', not a simple variable name. This means that we can do things
like:
let (x, y) = ( 1 , 2 );
After this expression is evaluated, x will be one, and y will be two. The
patterns are really powerful, and they have their own section in the book. We
don't need those facilities for now, let's just keep this in our minds as we go.
Rust is a statically typed language, which means that we specify our types in
advance, and these are checked at compile time. So why does our first
example compile? Well, Rust has this thing called 'type inference'. If you can
determine the type of something, Rust doesn't require you to type the type.
We can add the type if we want. The types come after a colon ( :) :
let x: i32 = 5 ;
If I asked the rest of the class to read this out loud, you would say " x is a
link to type i32 and the value five ."
In this case we decided to represent x as a 32-bit signed integer. Rust has
many different types of primitive integers. These start with i for signed
integers and u for unsigned integers. The possible sizes for integers are 8, 16,
32, and 64 bits.
In future examples, we could annotate the type in a comment. The example
would look like this:
fn main () {
let x = 5 ; // x: i32
}
Notice the similarities between the annotation and the syntax you use with let
. Including this kind of comment is not idiomatic in Rust, but we will include
it occasionally to help you understand what types Rust infers.
By default, links are immutable . This code will not compile:
let x = 5;
x = 10;
It will result in the following error:
error: re-assignment of immutable variable `x`
x = 10;
^ ~~~~~~
If you want a variable link to be mutable, you can use mut :
let mut x = 5 ; // mut x: i32
x = 10 ;
There is no single reason why variable bindings are immutable by default, but
we can think about it through one of Rust's main focuses: security. If you
forget to say mut . the compiler will notice this, and let you know that you
have mutated something you had no intention of mutating. If the links to
variables were mutable by default, the compiler would not be able to tell you
this. If you had the intention to mutate something, then the solution is very
easy: add mut .
There are other good reasons to avoid mutable state whenever possible, but
they are outside the scope of this guide. In general, you can often avoid
explicit mutation, and this is preferable in Rust. That said, sometimes,
mutation is just what you need, which is why it is not prohibited.
Let's go back to links to variables. Variable links in Rust have one more
aspect that differs from other languages: initialization to a value is required
before you can use them.
Let's put this to the test. Change your src / main.rs file so it looks like this:
fn main () {
let x: i32 ;
You can use cargo build on the command line to compile it. You will get a
warning but it will still print "Hello world!":
Compiling hello_world v0.0.1 (file: /// home / you / projects /
hello_world)
src / main.rs: 2: 9: 2:10 warning: unused variable: `x`, # [warn
(unused_variable)]
on by default
src / main.rs: 2 let x: i32;
^
Rust warns us that we never make use of the variable link, but because we
never use it, there is no danger, no fault. However, things change if we
actually try to use x . Let's do that. Change your program to look like this:
fn main () {
let x: i32;
$ build charge
Compiling hello_world v0. 0.1 (file: /// home / you / projects /
hello_world)
src / main.rs: 4 : 39 : 4 : 40 error: use of possibly uninitialized variable:
`x`
src / main.rs: 4 println! ( "The value of x is: {}" , x);
^
note: in expansion of format_args!
<std macros>: 2 : 23 : 2 : 77 note: expansion site
<std macros>: 1 : 1 : 3 : 2 note: in expansion of println!
src / main.rs: 4 : 5 : 4 : 42 note: expansion site
error: aborting due to previous error
Could not compile `hello_world`.
Rust will not allow you to use a value that has not been previously initialized.
Let's talk about what we've added to println below! .
If you include a key pair ( {} , some people call them mustaches / mustaches
...) in the string to print, Rust will interpret it as a request to interpolate some
kind of value. String interpolation is a term in computer science that means
"put this inside the string." We add a comma, and then x , to indicate that we
want this to be the interpolated value. The comma is used to separate the
arguments that we pass to the functions and the macros, in the case of passing
more than one.
When you use the curly braces, Rust will try to display the value in a way
that makes sense after checking its type. If we want to specify a more detailed
format, there are a wide number of options available . For now, we will stick
to the default behavior: integers are not very difficult to print.
Features
Every Rust program has at least one function, the main function :
fn main () {
}
This is the simplest possible function declaration. As we mentioned earlier,
fn says 'this is a function', followed by the name, parentheses because this
function does not receive any arguments, and then braces to indicate the body
of the function. Here's a function called foo :
fn foo () {
}
So what about receiving arguments? Here is a function that prints a number:
fn print_number (x: i32 ) {
println! ( "x is: {}" , x);
}
Here is a complete program that makes use of print_number :
fn main () {
print_number ( 5 );
}
// no type inference
let f: fn ( i32 ) -> i32 = mas_uno;
// type inference
let f = more_one;
We can then use f to call the function:
# fn mas_uno (i: i32 ) -> i32 {i + 1 }
# let f = more_one;
let six = f ( 5 );
Primitive Types
Rust has a set of types that are considered 'primitive'. This means that they
are integrated into the language. Rust is structured in such a way that the
standard library also provides a number of useful types based on primitives,
but these are the most primitive.
Boolean
Rust has a built-in Boolean type, called bool . It has two possible values true
and false :
let x = true ;
Numerical types
Rust has a variety of number types in a few categories: signed and unsigned,
fixed and variable, floating point, and integers.
These types consist of two parts: the category, and the size. For example, u16
is an unsigned type with a size of sixteen bits. More bits allow you to store
larger numbers.
If a number literal does not have anything that causes its type to be inferred,
default types are used:
let x = 42 ; // x has type i32
let a = [ 1 , 2 , 3 ];
x = y;
You can access the fields of a tuple through an unstructured let . Here is an
example:
let (x, y, z) = ( 1 , 2 , 3 );
Remember earlier when I said that the left side of a let statement was more
powerful than simply assigning a binding? Here we are. We can place a
pattern on the left side of a let , and if it matches the right side, we can assign
multiple bindings to a variable at once. In this case, the let "unstructures" or
"part" the tuple, and assigns the parts to the three bindings to variable.
This pattern is very powerful, and we will see it repeated frequently in the
future.
You can remove ambiguity between a single-element tuple and a value
enclosed in parentheses using a comma:
Indexed in tuples
You can also access the fields of a tuple with the indexing syntax:
let tuple = ( 1 , 2 , 3 );
let x = tuple. 0 ;
let y = tuple. 1 ;
let z = tuple. 2 ;
Features
Functions also have a type! They look like this:
fn foo (x: i32 ) -> i32 {x}
Comments
Now that we've seen some features, it's a good idea to learn about the
comments. Comments are notes you leave to other programmers in order to
explain some aspect of your code. The compiler mostly ignores them.
Rust has two types of comments that should interest you: line comments and
documentation comments
// Line comments are anything after '//' and extend to the end of the line
// If you have a long explanation about something, you can put comments
// line some together. Put a space between your // and your comment with
the
// in order to make them more readable.
The other type of comment is a documentation comment (or doc comment).
The doc comments use /// instead of // , and support Markdown notation
inside:
/// Add one to the number provided
///
/// # Examples
///
///
/// let five = 5; /// /// assert_eq! (6, sum_one (5)); /// # fn sum_one (x: i32) -
> i32 {/// # x + 1 /// #} /// `` `fn sum_one (x: i32) -> i32 {x + 1}
There is another style of comments, `//!`, With the purpose of commenting the container items (eg
crates, modules or functions), instead of the items that follow them. They are commonly used in the
root of a crate (lib.rb) or in the root of a module (mod.rs):
//! # The Rust Standard Library //! //! The Rust Standard Library provides the
functionality //! runtime essential for building software //! Rust portable. `` ''
When writing doc comments, providing some usage examples is very, very
useful. You will notice that we have made use of a macro: assert_eq! . It
compares two values, and panics! or if these are not the same. It is very useful
in documentation. There is also another macro, assert! , which panics! or if the
supplied value is false .
You can use the rustdoc tool to generate HTML documentation from those
comments, as well as run the code in the examples as tests!
if
Rust's approach to if is not particularly complex, but is more similar to the if
you will find in languages with dynamic typing than in traditional system
languages. Let's talk a little about this, to make sure you understand all the
nuances.
if is a specific form of a more general concept, the 'branch'. The name comes
from a branch in a tree: it is a decision point, in which depending on an
option, multiple paths can be taken.
In the case of if , there is a single choice that leads to two paths:
let x = 5;
if x == 5 {
println! ("x is five!");
}
Had the value of x changed to something different, this line would not have
been printed. To be more specific, if the expression after the if is evaluated
to true , then the code block is executed. If false , that block is not invoked.
If you want something to happen in the case of false , you must use an else :
let x = 5;
if x == 5 {
println! ("x is five!");
} else {
println! ("x is not five :(");
}
If there is more than one case, use an else if :
let x = 5;
if x == 5 {
println! ("x is five!");
} else if x == 6 {
println! ("x is six!");
} else {
println! ("x is neither five nor six :(");
}
This is all very standard. However, you can also do this:
let x = 5;
let y = if x == 5 {
10
} else {
fifteen
}; // y: i32
Which we can (and probably should) write like this:
let x = 5;
Cycles
Rust currently provides three approaches to performing iterative activity. loop
, while and for . Each of these approaches has its own uses.
loop
The infinite loop cycle is the simplest cycle available in Rust. Through the
use of the loop keyword , Rust provides a way to iterate indefinitely until
some termination statement is reached. The loop infinite Rust looks like this :
loop {
println! ("Itera forever!");
}
while
Rust also has a while loop . It looks like this:
while ! completed {
x+=x-3;
if x% 5 == 0 {
completed = true ;
}
}
While loops are the right choice when you are not sure how many times you
need to iterate.
If you need an infinite loop, you might be tempted to write something like
this:
while true {
However, loop is by far the best one for this case:
loop {
Rust's control flow analysis treats this construction differently than a while true
, because we know that we will iterate forever. In general, the more
information we provide to the compiler, the compiler could perform better in
relation to security and code generation, that is why you should always prefer
loop when you plan to iterate indefinitely.
for
The for loop is used to iterate a particular number of times. Cycles for Rust,
however, work differently from other programming languages way systems.
The for Rust does not look like this cycle for a "C style"
for (x = 0 ; x < 10 ; x ++) {
printf ( "% d \ n" , x);
}
Instead, Rust's for loop looks like this:
for x in 0 .. 10 {
println! ( "{}" , x); // x: i32
}
In slightly more abstract terms:
for var in expression {
code
}
The expression is an iterator . The iterator returns a series of elements. Each
element is an iteration of the cycle. That value is in turn assigned to the name
var , which is valid in the body of the cycle. Once the cycle has finished, the
next value is obtained from the iterator, and is iterated once more. When
there are no more values, the for loop ends.
In our example, 0..10 is an expression that takes a start position and an end
position, and returns an iterator above those values. The upper limit is
exclusive, so our loop will print from 0 to 9 , not 10 .
Ruth does not possess the C-style for loop , by the way. Manually controlling
each element of the cycle is complicated and error prone, even for
experienced C programmers.
List yourself
When you need to keep track of how many times you've iterated, you can use
the .enumerate () function .
In ranges:
for (i, j) in ( 5 .. 10 ) .enumerate () {
println! ( "i = {} and j = {}" , i, j);
}
Departure:
i = 0 and j = 5
i = 1 and j = 6
i = 2 and j = 7
i = 3 and j = 8
i = 4 and j = 9
Don't forget to put the parentheses around the range.
In iterators:
# let lines = "hello \ nworld" .lines ();
for (line_number, line) in lineas.enumerate () {
println! ( "{}: {}" , line_number, line);
}
Outputs:
0: Contents of line one
1: Contents of line two
2: Contents of line three
3: Contents of line four
while ! completed {
x+=x-3;
if x% 5 == 0 {
completed = true ;
}
}
We need to maintain a binding variable mut , completed , to know when we
should get out of the cycle. Rust has two keywords to help us modify the
iteration process: break and continue .
In this case, we can write the cycle in a better way with break :
let mut x = 5 ;
loop {
x+=x-3;
if x% 5 == 0 { break ; }
}
We now loop indefinitely and use break to break the loop early. Using an
explicit return also works well for early cycle termination.
continue is similar, but instead of ending the loop, it makes us go to the next
iteration. The following will print the odd numbers:
for x in 0 .. 10 {
if x% 2 == 0 { continue ; }
Loop tags
You might also encounter situations where you have nested loops and need to
specify which loop your break or continue statements belong to . As in most
languages, by default a break or continue applies to the innermost loop. In the
event that you want to apply a break or continue to any of the external cycles,
you can use labels to specify to which cycle the break or continue statement
applies . The following will only print when both x and y are odd:
'exterior : for x in 0 .. 10 {
'interior : for and in 0 .. 10 {
if x% 2 == 0 { continue 'exterior ; } // continue the loop above x
if and% 2 == 0 { continue 'inside ; } // continue the loop above and
println! ( "x: {}, y: {}" , x, y);
}
}
Belonging
This guide is one of three introducing the Rust membership system. This is
one of the most unique and irresistible features of Rust, with which Rust
developers should be familiar. It is through membership that Rust achieves
his most important goal, security. There are a few different concepts, each
with its own chapter:
membership, the one you currently read
loan , and its associated characteristic 'references'
life time , an advanced loan concept
These three chapters are related, in order. You will need all three to fully
understand Rust's membership system.
Goal
Before going into detail, two important notes about the membership system.
Rust is focused on safety and speed. Rust achieves these goals through many
'zero cost abstractions', which means that at Rust, abstractions cost as little as
possible to make them work. The membership system is a prime example of a
zero cost abstraction. All the analysis that we will be talking about in this
guide is carried out at compilation time . You do not pay any cost at runtime
for any of these facilities.
However, this system has a certain cost: the learning curve. Many new Rust
users experience something we call 'fighting with the borrow checker', a
situation in which the Rust compiler refuses to compile a program which the
author thinks is valid. This occurs frequently because the programmer's
mental model of how membership works does not conform to the current
rules implemented in Rust. You probably experience similar things in the
beginning. However, there is good news: other experienced Rust developers
report that once they work with membership system rules for a period of
time, they struggle less and less with the loan checker.
With that in mind, let's learn about belonging.
Belonging
The Bindings variable holding property Rust: These 'have membership' what
are associated. This means that when a binding to a variable goes out of
scope, Rust releases the resources associated with it. For example:
fn foo () {
let v = vec! [ 1 , 2 , 3 ];
}
When v comes into scope, a new Vec <T> is created. In this case the vector
also allocates some memory from the mound , for the three elements. When v
goes out of scope at the end of foo () , Rust will clear everything related to the
vector, including the memory allocated from the mound. This occurs
deterministically, at the end of the scope.
Motion semantics
There is something more subtle here, Rust makes sure that there is only
exactly one binding to any particular resource. For example, if we have a
vector, we can assign it to another binding to variable:
let v = vec! [ 1 , 2 , 3 ];
let v2 = v;
let v2 = v;
take (v);
let v = vec! [ 1 , 2 , 3 ];
let v2 = v;
The first line allocates memory for the vector object, v , and for the data it
contains. The vector object is then stored in the stack stack and contains a
pointer to the content ( [1, 2, 3] ) stored in the mound . When we move v to
v2 , a copy of that pointer is created for v2 . All this means that there would
be two pointers for the vector content in the mound. This violates Rust's
safety guarantees, introducing a race condition. That is why Rust prohibits
the use of v after the movement has been carried out.
Importantly, some optimizations might remove the copy of the bytes on the
stack, depending on certain circumstances. So it may not be as inefficient as
it initially looks.
Copy Types
We have established that when we transfer the membership to another
binding to variable, we cannot use the original binding. However, there is a
trait trait that changes this behavior, it is called Copy . We haven't discussed
traits yet, but for now you can see them as an annotation made to a particular
type that adds extra behavior. For example:
let v = 1 ;
let v2 = v;
fn main () {
let a = 5 ;
fn foo (v: Vec < i32 >) -> Vec < i32 > {
// do something with v
// returning membership
v
}
Things would get quite tedious. It gets even worse as we have more things
that we want to belong to:
fn foo (v1: Vec < i32 >, v2: Vec < i32 >) -> ( Vec < i32 >, Vec < i32 >, i32 )
{
// do something with v1 and v2
let v1 = vec! [ 1 , 2 , 3 ];
let v2 = vec! [ 1 , 2 , 3 ];
Ugh! The return type, the return line, and the function call become much
more complicated.
Fortunately, Rust offers a facility, the loan, a facility that helps us solve this
problem.
It is the topic of the next section!
These three chapters are related, and in order. You will need to read all three
to fully understand the membership system.
Goal
Before going into detail, two important notes about the membership system.
Rust is focused on safety and speed. Rust achieves these goals through many
'zero cost abstractions', which means that at Rust, abstractions cost as little as
possible to make them work. The membership system is a prime example of a
zero cost abstraction. All the analysis that we will be talking about in this
guide is carried out at compilation time . You do not pay any cost at runtime
for any of these facilities.
However, this system has a certain cost: the learning curve. Many new Rust
users experience something we call 'fighting with the borrow checker', a
situation in which the Rust compiler refuses to compile a program which the
author thinks is valid. This occurs frequently because the programmer's
mental model of how membership works does not conform to the current
rules implemented in Rust. You probably experience similar things in the
beginning. However, there is good news: other experienced Rust developers
report that once they work with membership system rules for a period of
time, they struggle less and less with the loan checker.
With that in mind, let's learn about the loan.
Loan
At the end of the membership section , we had an ugly function that looked
like this:
fn foo (v1: Vec < i32 >, v2: Vec < i32 >) -> ( Vec < i32 >, Vec < i32 >, i32 )
{
// do something with v1 and v2
let v1 = vec! [ 1 , 2 , 3 ];
let v2 = vec! [ 1 , 2 , 3 ];
The above, however, is not idiomatic Rust, since it does not benefit from the
advantages of the loan. Here is the first step:
fn foo (v1: & Vec < i32 >, v2: & Vec < i32 >) -> i32 {
// do something with v1 and v2
let v1 = vec! [ 1 , 2 , 3 ];
let v2 = vec! [ 1 , 2 , 3 ];
fails with:
}
^
Apparently there are rules.
The rules
Here are the rules about the loan at Rust:
First, any loan must live in a realm no larger than that of the owner. Second,
you can have one or the other of these types of loans, but not both at the same
time:
one or more references ( & T ) to a resource,
exactly a mutable reference ( & mut T ).
You may notice that this is very similar, but not exactly the same, to the
definition of a race condition:
There is a 'race condition' when two or more pointers access the same
memory location at the same time, where at least one is writing, and the
operations are not synchronized.
With the references, you can have as many as you want, since none of them
are writing. If you are writing, and you need two or more pointers to the same
memory, you can have only one & mut at a time. This is how Rust prevents
race conditions at compile time: we will get errors if we break the rules.
With this in mind, let's consider our example again.
* and + = 1;
}
^
In other words, the mutable loan is maintained throughout the rest of our
example. What we want is for our mutable loan to end before we try to call
println! and let's make an immutable loan. At Rust, the loan is associated with
the area in which the loan is valid. Our areas look like this:
let mut x = 5;
let mut x = 5 ;
{
let y = & mut x; // - + x loan & mut starts here
* and + = 1 ; // |
} // - + ... and ends here
Iterator Invalidation
An example is 'iterator override', which occurs when you try to mutate a
collection while iterating over it. Rust's loan checker prevents this from
happening:
for i in & v {
println! ( "{}" , i);
}
The above prints from one to three. As we iterate the vectors, we are only
provided with references to their elements. v itself is immutably borrowed,
which means we can't change it while iterating:
for i in & v {
println! ("{}", i);
v.push (34);
}
note: ... but borrowed value is only valid for the block suffix following
statement 0 at 4:18
let x = 5;
y = & x;
}
In other words, y is valid only for the area where x exists. As soon as x
leaves, reference is invalid. That is why the error says that the loan, 'does not
live long enough' ('does not live long enough') since it is not valid for the
correct amount of time.
The same problem occurs when the reference is declared before the variable
to which it refers. This is because the resources within the same scope are
released in the opposite order to the order in which they were declared:
let y: & i32;
let x = 5;
y = & x;
note: ... but borrowed value is only valid for the block suffix following
statement 1 at 3:14
let x = 5;
y = & x;
Life Times
This guide is one of three introducing the Rust membership system. This is
one of the most unique and attractive features of Rust, with which Rust
developers should be well acquainted. Membership is how Rust achieves its
highest goal, memory management security. There are a few different
concepts, each with its own chapter:
membership , the main concept
[loan] [borrowing], and its associated characteristic 'references'
times of life , the one you read now
These three chapters are related, and in order. You will need to read all three
to fully understand the membership system.
Goal
Before going into detail, two important notes about the membership system.
Rust is focused on safety and speed. Rust achieves these goals through many
'zero cost abstractions', which means that at Rust, abstractions cost as little as
possible to make them work. The membership system is a prime example of a
zero cost abstraction. All the analysis that we will be talking about in this
guide is carried out at compilation time . You do not pay any cost at runtime
for any of these facilities.
However, this system has a certain cost: the learning curve. Many new Rust
users experience something we call 'fighting with the borrow checker', a
situation in which the Rust compiler refuses to compile a program which the
author thinks is valid. This occurs frequently because the programmer's
mental model of how membership works does not conform to the current
rules implemented in Rust. You probably experience similar things in the
beginning. However, there is good news: other experienced Rust developers
report that once they work with membership system rules for a period of
time, they struggle less and less with the loan checker.
With that in mind, let's learn about life times.
Life times
Lending a reference to another resource that someone else owns can be
tricky. For example, imagine this set of operations:
I get a handle to some kind of resource.
I lend you a reference to the resource.
I decide that I am done with the resource, and release it, while you still have the
reference to it.
You decide to use the resource.
// explicit
fn bar < 'a > (x: & ' a i32 ) {
}
The 'a is read' the life time a '. Technically, every reference has a lifetime
associated with it, but the compiler allows you to omit them in common
cases. Before we get to that, let's take a look at the explicit piece of code:
fn bar <'a> (...)
We talked a bit about function syntax earlier , but we didn't discuss <> s after
a function name. A function can have 'generic parameters' between the <> s,
and lifetimes are a type of generic parameter. We will discuss other types of
generics later in the book , but for now, let's focus only on the life time
aspect.
We use <> to declare our life times. This says that bar has a life time, 'a .
Had it had references as parameters, it would have looked like this:
fn bar <'a,' b = ""> (...)
So in our parameter list, we use the lifetimes that we have named:
... (x: & 'a i32)
If we wanted a referral & mut , we could have done the following:
... (x: & 'a mut i32)
If you compare & mut i32 with & 'to mut i32 , they are the same, it's just that
time of life ' to have gotten between & and mut i32 . We read & mut i32 as 'a
mutable reference to an i32 ' and & 'to mut i32 as' a mutable reference to an i32
with the lifetime 'a '.
In struct s
You will also need explicit lifetime times when working with struct s:
struct Foo < 'a > {
x: & 'to i32 ,
}
fn main () {
let y = & 5 ; // this is the same as `let _y = 5; let y = & _y; `
let f = Foo {x: y};
impl blocks
Let's implement a method in Foo :
struct Foo < 'a > {
x: & 'to i32 ,
}
fn main () {
let y = & 5 ; // this is the same as `let _y = 5; let y = & _y; `
let f = Foo {x: y};
fn main () {
let y = & 5 ; // - + and enter scope
let f = Foo {x: y}; // - + f enters scope
// stuff // |
// |
} // - + fyy go out of scope
Our f lives within the scope of y , that is why everything works. What
would happen otherwise? The following code would not work:
struct Foo <'a> {
x: & 'to i32,
}
fn main () {
let x; // - + x enters scope
// |
{// |
let y = & 5; // --- + and enter scope
let f = Foo {x: y}; // --- + f enters scope
x = & f.x; // | | mistake here
} // --- + fyy go out of scope
// |
println! ("{}", x); // |
} // - + x goes out of scope
Uff! As you can see here, the scopes of f and y are less than the scope of x .
But when we do x = & f.x , we make x a reference to something to be out of
scope.
Named lifetimes are a way to give these settings a name. Giving something a
name is the first step toward being able to talk about it.
'static
The lifetime called 'static' is a special lifetime. This points out that something
has the lifetime of the entire program. Most Rust developers know 'static
when dealing with character strings:
let x: & 'static str = "Hello, world." ;
Character string literals have the & 'static str type since the reference is always
alive: these are placed in the data segment of the final binary. Another
example is the global ones:
static FOO: i32 = 5 ;
let x: & 'static i32 = & FOO;
The above adds an i32 to the data segment of the binary, and x is a reference
to it.
Examples
Here are some examples of functions with elided lifetimes. We have paired
each example of an elided life time with its expanded form.
fn print (s: & str); // elided
fn print <'a> (s: &' a str); // expanded
// In the example above, `lvl` does not need a lifetime because it is not a
reference (` & `). Only things related to references (like a `struct` that
contains a reference) need lifetime.
fn substr (s: & str, until: u32) -> & str; // elided
fn substr <'a> (s: &' a str, until: u32) -> & 'a str; // expanded
fn frob (s: & str, t: & str) -> & str; // ILLEGAL, two inputs
fn frob <'a,' b = ""> (s: & 'a str, t: &' b str) -> & str; // Expanded:
Output lifetime is ambiguous
fn args (& mut self, args: & [T]) -> & mut Command // elided
fn args <'a,' b, = "" T: ToCStr = ""> (& 'a mut self, args: &' b [T]) -> &
'a mut Command // expanded
x = 6 ; // no problem!
This is a link to mutable variable. When a link to variable is mutable, it
means that you are allowed to change what the link points to. So, in the
example above, you are not changing the value at x , instead, the link
changed from one i32 to another.
If you want to change what the link points to variable, you will need a
mutable reference :
let mut x = 5 ;
let y = & mut x;
y is an immutable variable link to a mutable reference, which means you can't
associate y to something else ( y = & mut z ), but you can mutate whatever y
is associated with ( * y = 5 ). A very subtle difference.
Of course, if you need both:
let mut x = 5 ;
let mut y = & mut x;
Now and it may be associated with another value, and the value that this
referencing can be changed.
It is important to note that mut is part of a pattern , so that you can do things
like:
let ( mut x, y) = ( 5 , 6 );
fn foo ( mut x: i32 ) {
#}
ax = 10;
struct Point {
x: i32 ,
y: Cell < i32 >,
}
dot.y.set ( 7 );
Structures
Structures ( struct s) are a way to create more complex data types. For
example, if we were doing calculations involving coordinates in 2D space,
we would need both an x value and a y value :
let origin_x = 0 ;
let origin_y = 0 ;
A struct allows us to combine both into a single unified data type:
struct Point {
x: i32 ,
y: i32 ,
}
fn main () {
let origin = Point {x: 0 , y: 0 }; // origin: Point
fn main () {
let mut dot = Dot {x: 0 , y: 0 };
dot.x = 5 ;
fn main () {
let mut dot = Dot {x: 0, y: 0};
dot.x = 5;
let dot = dot; // now, this new link to variable cannot be changed
Update syntax
A struct can include .. to indicate that you want to use a copy of some other
struct for some of the values. For example:
struct Point3d {
x: i32 ,
y: i32 ,
z: i32 ,
}
This assigns a new y point , but maintains the old values of x and z . It does
not have to be the same structure, you can make use of this syntax when you
create new ones, and it will copy the values you do not specify:
# struct Punto3d {
# x: i32 ,
# y: i32 ,
# z: i32 ,
#}
let origin = Point3d {x: 0 , y: 0 , z: 0 };
let dot = Dot3d {z: 1 , x: 2 , .. origin};
struct Point {
x: i32 ,
y: i32 ,
z: i32 ,
}
Now, we have names, instead of positions. Good names are important, and
with a struct , we have names.
There is a case in which a structure tuple is very useful, and it is a structure
tuple with a single element. Called pattern nuevotipo ( newtype ), since they
create a new type, different from the value it contains, expressing semantics
itself:
struct Inches ( i32 );
Unit-like structs
You can define a struct without any member:
struct Electron;
This struct is called type-unit ( unit-like ) because of its similarity to the empty
tuple, () , sometimes called unit ( unit ). As a tuple structure, it defines a new
type.
The above is rarely useful in itself (although it can sometimes serve as a
marker type), but in combination with other features, it can become useful.
For example, a library may require creating a structure that implements a
certain trait to handle events. If you don't have any data to save in the
structure, you can simply create a unit-type struct .
Enumerations
An enum ( enum ) in Rust is a type that represents data that can be one of a set
of possible variants:
enum Message {
Leave,
ChangeColor ( i32 , i32 , i32 )
Move {x: i32 , y: i32 },
Write ( String ),
}
Each variant can optionally have associated data. The syntax for defining
variants is similar to the syntax used to define structures ( struct s): you can
have variants without data (such as unit-type structures), variants with named
data, and variants with unnamed data (such as tuple structures). ). However,
unlike structure definitions, an enum is a single type. An enum value can
match any of the variants. For this reason, an enum is sometimes called a
'sum type': the set of possible values of the enum is the sum of the sets of
possible values for each variant.
We use the :: syntax to make use of each variant: the variants are within the
scope of the enum . Which makes the following valid:
# enum Message {
# Move {x: i32 , y: i32 },
#}
let x: Message = Message :: Move {x: 3 , y: 4 };
Constructors as functions
A constructor of an enum can also be used as a function. For example:
# enum Message {
# Write ( String ),
#}
let m = Message :: Write ( "Hello, world" .to_string ());
Is the same as
# enum Message {
# Write ( String ),
#}
fn foo (x: String ) -> Message {
Message :: Write (x)
}
let v1: Vec <Message> = v.into_iter (). map (Message :: Write) .collect ();
Match
Often a simple if / else is not enough, because you have more than two
possible options. Also, the conditions can be complex. Rust has a reserved
word, match , which allows you to replace complicated if / else constructions
with something more powerful. Check out:
let x = 5 ;
match x {
1 => println! ( "one" ),
2 => println! ( "two" ),
3 => println! ( "trees" ),
4 => println! ( "four" ),
5 => println! ( "five" ),
_ => println! ( "something else" ),
}
match takes an expression and then forks based on its value. Each branch arm
has the form value => expression . When the value matches, the expression of the
arm is evaluated. It is called match by the term 'pattern matching', of which
match is an implementation. There is an entire section about patterns that
covers all possible patterns.
So what is the big advantage? Well, there are a few. First of all, match
imposes check exhaustion ( 'exhaustiveness checking'). Do you see the last arm,
the one with the underscore ( _ )? If we remove that arm, Rust will give us
an error:
error: non-exhaustive patterns: `_` not covered
In other words, Rust is trying to tell us that we forgot a value. Because x is
an integer, Rust knows that x can have a number of different values - for
example, 6 . Without the _ , however, there is no matching arm, and
consequently Rust refuses to compile the code. _ acts as an 'arm that catches
everything'. If none of the other arms match, the arm with the _ will, and
since we have said 'catch everything arm', we now have an arm for every
possible value of x , and as a result, our program will compile successfully.
match is also an expression, which means that we can use it on the right side of
a let or directly where an expression is used:
let x = 5 ;
fn exit () { / * ... * / }
fn change_color (r: i32 , g: i32 , b: i32 ) { / * ... * / }
fn move_cursor (x: i32 , y: i32 ) { / * ... * / }
Vector
A 'vector' is a dynamic array, implemented as the type of the standard Vec
<T> library . The T means that we can have vectors of any type (take a look
at the [generics] [generics] chapter for more information). Vectors always
house their data on the mound. You can create vectors with the vec macro ! :
let v = vec! [ 1 , 2 , 3 , 4 , 5 ]; // v: Vec <i32>
(Note that unlike the println! Macro we have used in the past, we use square
brackets [] with the vec! Macro . Rust allows you to use any in any situation,
this time it is purely by convention)
There is an alternative form of vec! to repeat an initial value:
let v = vec! [ 0 ; 10 ]; // ten zeros
Accessing elements
To get the value at a particular index of the vector, we use [] s:
let v = vec! [ 1 , 2 , 3 , 4 , 5 ];
for i in & v {
println! ( "A reference to {}" , i);
}
for i in & mut v {
println! ( "A mutable reference to {}" , i);
}
for i in v {
println! ( "Taking membership of the vector and its element {}" , i);
}
Character Strings
Character strings are an important concept to master for any programmer.
The character string handling system in Rust is slightly different from that of
other languages, due to its focus on system programming. As long as you
have a variable-size data structure, things can get a little difficult, and
character strings are a data structure that can vary in size. That said, Rust's
character strings also work differently than in some other system
programming languages, such as C.
Let's go into the details. A 'character string' ('string') is a sequence of Unicode
scalar values encoded as a stream of UTF-8 bytes. All character strings are
guaranteed to be a valid encoding of UTF-8 sequences. Additionally, and
unlike other system languages, character strings are not null-terminated and
can contain null bytes.
Rust has two main types of character strings: & str and String . Let's talk about
& str first . These are called 'string slices'. String literals are of type & 'static
str`:
let greeting = "Hello." ; // greeting: & 'static str
This character string is statically assigned, meaning that it is stored within our
compiled program, and exists for the full duration of its execution. The
greeting link is a reference to an aesthetically assigned string. Pieces of
character strings are of a fixed size, and cannot be mutated.
A String , on the other hand, is a character string assigned from the mound.
This chain can grow, and it is also guaranteed to be UTF-8. The String are
commonly created through conversion of a piece of character string using the
method to_string .
let mut s = "Hello" .to_string (); // mut s: String
println! ( "{}" , s);
fn main () {
let s = "Hello" .to_string ();
receive_piece (& s);
}
This coercion does not occur for functions that accept one of the traits & str ’s
instead of & str . For example, TcpStream :: connect has a parameter of type
ToSocketAddrs . A & str is fine but a String must be explicitly converted using &
* .
Indexed
Because the character strings are valid UTF-8, they do not support indexing:
let s = "hello";
for b in hachiko.as_bytes () {
print! ( "{}," , b);
}
println! ( "" );
for c in hachiko.chars () {
print! ( "{}," , c);
}
println! ( "" );
The above prints:
229, 191, 160, 231, 138, 172, 227, 131, 143, 227, 131, 129, 229, 133, 172,
忠, 犬, ハ, チ, 公,
As you can see, there are more bytes than characters ( char s).
You can get something similar to an index like this:
# let hachiko = " 忠 犬 ハ チ 公" ;
let dog = hachiko.chars (). nth ( 1 ); // something like hachiko [1]
This emphasizes that we have to walk from the beginning of the chars list .
Slicing
You can get a piece of a character string with the cut syntax:
let dog = "hachiko" ;
let hachi = & dog [ 0 .. 5 ];
But note that these are byte offsets, not character offsets . So the following
will fail at runtime:
let dog = " 忠 犬 ハ チ 公";
let hachi = & dog [0..2];
with this error:
thread '' panicked at 'index 0 and / or 2 in ` 忠 犬 ハ チ 公 ` do not lie on
character boundary '
Concatenation
If you have a String , you can concatenate a & str at the end:
let hello = "Hello" .to_string ();
let mundo = "world!" ;
Generic
Sometimes when writing a function or data structure, we might wish that it
could work with multiple types of arguments. At Rust we can achieve this
through generics. Generics are called 'parametric polymorphism' in type
theory, which means that they are types or functions that have multiple
shapes ('poly' for multiple, 'morph' for shape) on a certain parameter
('parametric').
Either way, enough about type theory, let's look at some generic code. The
standard Rust library provides a type, Option <T> , which is generic:
enum Option <T> {
Some (T),
None ,
}
The <T> part , which you have seen a few times before, indicates that this is a
generic data type. Within the declaration of our enum, wherever we see a T
we substitute that type for the same type used in the generic one. Here's an
example of using Option <T> , with some extra annotations:
let x: Option < i32 > = Some ( 5 );
In the type declaration, we say Option <i32> . Note how similar this looks to
Option <T> . So in this Option , T has the value of i32 . On the right side of the
binding, we make a Some (T) , where T is 5 . Because 5 is an i32 , both sides
match, and Rust is happy. If they did not match, we would have got an error:
let x: Option = Some (5);
// error: mismatched types: expected `core :: option :: Option`,
// found `core :: option :: Option <_>` (expected f64 but found integral
variable)
That doesn't mean we can't create Option <T> s that contain an f64 . They
simply must match:
let x: Option < i32 > = Some ( 5 );
let y: Option < f64 > = Some ( 5.0f64 );
Very good. One definition, multiple uses.
Generics do not necessarily have to be generic on a single type. Consider
another similar type in Rust's standard library, Result <T, E> :
enum Result <T, E> {
Ok (T),
Err (E),
}
This type is generic on two types: T and E . By the way, capital letters can
be any. We could have defined Result <T, E> as:
enum Result <A, Z> {
Ok (A),
Err (Z),
}
to have wanted. The convention says that the first generic parameter must be
T , of 'type', and that we use E for 'error'. Rust, however, does not care.
The Result <T, E> type is used to return the result of a computation, with the
possibility of returning an error in the event that such computation has not
been successful.
Generic functions
We can write functions that take generic types with a similar syntax:
fn receives_any_thing <T> (x: T) {
// do something with x
}
The syntax has two parts: the <T> says "this function is generic on a type, T
", and the x: T part says "x has the type T. "
Multiple arguments can have the same type:
fn receives_two_things_from_the same_type <T> (x: T, y: T) {
// ...
}
We could have written a version that receives multiple types:
fn receives_two_things_from_different_types <T, U> (x: T, y: U) {
// ...
}
Generic structures
You can also store a generic type in a structure:
struct Point <T> {
x: T,
y: T,
}
impl Circle {
fn area (& self ) -> f64 {
std :: f64 :: consts :: PI * ( self .radio * self .radio)
}
}
The traits are similar, except that we define a trait with only the method
signature and then implement the trait for the structure. So:
struct Circle {
x: f64 ,
y: f64 ,
radius: f64 ,
}
trait HasArea {
fn area (& self ) -> f64 ;
}
struct Circle {
x: f64 ,
y: f64 ,
radius: f64 ,
}
Square struct {
x: f64 ,
y: f64 ,
side: f64 ,
}
fn main () {
let c = Circle {
x: 0.0f64 ,
y: 0.0f64 ,
radius: 1.0f64 ,
};
let s = Square {
x: 0.0f64 ,
y: 0.0f64 ,
side: 1.0f64 ,
};
print_area (c);
print_area (s);
}
This program produces the output:
This figure has an area of 3.141593
This figure has an area of 1
As you can see, print_area is now generic, but it also ensures that we have
provided the correct types. If we pass an incorrect type:
print_area (5);
We get a compile-time error:
error: the trait `HasArea` is not implemented for the type` _` [E0277]
fn main () {
let mut r = Rectangle {
x: 0 ,
y: 0 ,
width: 47 ,
height: 47 ,
};
r.height = 42 ;
assert! (! r.es_cuadrado ());
}
needs to check that the sides are equal, and for this the types must
is_square ()
be of a type that implements the trait core :: cmp :: PartialEq :
Impl Rectangle {...}
Now, a rectangle can be defined based on any type that can be compared by
equality.
We have defined a new Rectangle structure that accepts numbers of any
precision, objects of any type as long as they can be compared by equality.
Could we do the same for our HasArea , Square and Circle structures ? Yes, but
they need multiplication, and to work with that we need to know more about
the operator traits .
* self as f64
}
}
5 .area ();
Implementing methods on those primitive types is considered poor style,
even when possible.
This may look like the old west, but there are two restrictions on
implementing traits that prevent things from spiraling out of control. The first
is that if the trait is not defined in your scope, it does not apply. Here's an
example: the standard library provides a Write trait that adds extra
functionality to File s, making file I / O possible. By default, a File would not
have its methods:
let mut f = std :: fs :: File :: open ("foo.txt"). ok (). expect ("Failed to
open foo.txt");
let buf = b "anything"; // byte string literal. buf: & [u8; 8]
let result = f.write (buf);
# result.unwrap (); // ignore the error
Here is the error:
error: type `std :: fs :: File` does not implement any method in scope
named` write`
let result = f.write (buf);
^ ~~~~~~~~~
We need to first use the Write trait :
use std :: io :: Write;
fn main () {
foo ( "Hello" , "world" );
bar ( "Hello" , "world" );
}
uses the syntax previously demonstrated, and bar () uses a where clause .
foo ()
All you need to do is leave the limits out when you define your type
parameters and then add a where after the parameter list. For longer lists,
blank spaces can be added:
use std :: fmt :: Debug;
x.clone ();
y.clone ();
println! ( "{:?}" , and);
}
Such flexibility can add clarity in complex situations.
The where clause is also more powerful than the simplest syntax. For
example:
Trait become <Output> {
fn convert (& self ) -> Output;
}
Default methods
If you already know how a typical implementer will define a method, you can
allow your trait to provide a default method:
trait Foo {
fn is_valid (& self ) -> bool ;
# trait Foo {
# fn is_valid (& self ) -> bool ;
#
# fn is_invalid (& self ) -> bool {! self .es_valido ()}
#}
struct UsaDefault;
struct OverwriteDefault;
let on = OverwriteDefault;
assert! (sobre.is_invalid ()); // prints "OverwriteDefault.com_invalid
call!"
Heritage
Sometimes implementing one trait requires implementing another:
trait Foo {
fn foo (& self );
}
Drop
Now that we've discussed traits, let's talk about a particular trait provided by
the standard Rust library, Drop . The Drop trait provides a way to execute code
when a value goes out of scope. For example:
struct HasDrop;
fn main () {
let x = HasDrop;
// let's do something
fn main () {
let firecracker = Explosive {power: 1 };
let tnt = Explosive {power: 100 };
}
The above will print:
BOOM multiplied by 100 !!!
BOOM multiplied by 1 !!!
The TNT goes first than the firecracker, because it was created later. Last to
enter, first to exit.
So what is Drop good for ? Generally, it is used to clean up any resource
associated with a struct . For example, the type Arc <T> is a guy with
reference counting. When Drop is called, it will decrement the reference
count, and if the total number of references is zero, it will clear the
underlying value.
if let
allows you to combine if and let to reduce the cost of some types of
if let
pattern matching.
For example, let's say we have some kind of Option <T> . We want to call a
function on it if it is a Some <T> , but do nothing if it is None . It would be
something like this:
# let option = Some ( 5 );
# fn foo (x: i32 ) {}
match option {
Some (x) => {foo (x)},
None => {},
}
We don't have to use match here, for example we could use if :
# let option = Some ( 5 );
# fn foo (x: i32 ) {}
if option.is_some () {
let x = option.unwrap ();
foo (x);
}
Neither of these two options is particularly attractive. We can use if let to do
the same, but in a better way:
# let option = Some ( 5 );
# fn foo (x: i32 ) {}
if let Some (x) = option {
foo (x);
}
If a pattern matches well, it associates any appropriate part of the value with
the identifiers in the pattern, and then evaluates the expression. If the pattern
does not match, nothing happens.
If you want to do something in case the pattern doesn't match, you can use
else :
while let
Similarly, while let can be used when you want to conditionally iterate as long
as the value matches a certain pattern. Convert code like this:
# let option: Option < i32 > = None ;
loop {
match option {
Some (x) => println! ( "{}" , x),
_ => break ,
}
}
In code like this:
# let option: Option < i32 > = None ;
while let Some (x) = option {
println! ( "{}" , x);
}
Trait Objects
When the code involves polymorphism, a mechanism is needed to determine
which specific version should be run. This mechanism is called an office .
There are two major forms of dispatch: static dispatch and dynamic dispatch.
While it is true that Rust prefers static dispatching, it also supports dynamic
dispatching through a mechanism called 'trait objects'.
Bases
For the rest of this chapter, we will need a trait and some implementations.
Let's create a simple one, Foo . Foo has a single method that returns a String .
trait Foo {
fn method (& self ) -> String ;
}
We will also implement this trait for u8 and String :
# trait Foo { fn method (& self ) -> String ; }
impl Foo for u8 {
fn method (& self ) -> String { format! ( "u8: {}" , * self )}
}
Static dispatch
We can use trait to perform static dispatch by using trait limits:
# trait Foo { fn method (& self ) -> String ; }
# impl Foo for u8 { fn method (& self ) -> String { format! ( "u8: {}" , * self )}}
# impl Foo for String { fn method (& self ) -> String { format! ( "string: {}" , * self )}}
fn do_something <T: Foo> (x: T) {
x.method ();
}
fn main () {
let x = 5u8 ;
let y = "Hello" .to_string ();
do_something (x);
do_something (y);
}
Rust uses 'monomorphization' for static dispatch in this code. Which means
you will create a special version of do_something () for both u8 and String , then
replacing the call places with calls to these specialized functions. In other
words, Rust generates something like this:
# trait Foo { fn method (& self ) -> String ; }
# impl Foo for u8 { fn method (& self ) -> String { format! ( "u8: {}" , *
self )}}
# impl Foo for String { fn method (& self ) -> String { format! ( "string:
{}" , * self )}}
fn do_something_u8 (x: u8 ) {
x.method ();
}
fn main () {
let x = 5u8 ;
let y = "Hello" .to_string ();
do_something_u8 (x);
do_something_string (y);
}
The above has a great advantage: static dispatch allows function calls to be
inserted online because the receiver is known at compile time, and online
insertion is key to good optimization. Static dispatch is fast, but it comes with
a downside: 'code bloat', as a result of repeated copies of the same function
being inserted into the binary, one for each type.
Also, compilers are not perfect and can "optimize" code by slowing it down.
For example, functions inserted online in an anxious way inflate the
instruction cache (and the cache governs everything around us). This is why
# [inline] and # [inline (always)] should be used with caution, and one reason why
using dynamic dispatch is sometimes more efficient.
However, the common case is that static dispatch is more efficient. One can
have a thin wrapper function dispatched statically by performing dynamic
dispatch, but not vice versa, ie; static calls are more flexible. This is why the
standard library tries to be dynamically dispatched whenever possible.
Dynamic dispatch
Rust provides dynamic dispatch through a facility called 'trait objects'. Trait
objects, like & Foo or Box <Foo> , are normal values that store a value of any
type that implements the given trait, where the precise type can only be
determined at runtime.
A trait object can be obtained from a pointer to a specific type that
implements the trait by converting it (eg & x as & Foo ) or applying coercion
(eg using & x as an argument to a function that receives & Foo ).
Those coercions and conversions also work for pointers like & mut T a & mut
Foo and Box <T> a Box <Foo> , but that's it so far. Coercions and conversions
are identical.
This operation can be seen as the 'deletion' of the compiler's knowledge about
the specific type of the pointer, and that is why traits objects are sometimes
referred to as type deletion .
Going back to the previous example, we can use the same trait to perform
dynamic dispatch with conversion of trait objects:
fn main () {
let x = 5u8 ;
do_something (& x as & Foo);
}
or through coercion:
# trait Foo { fn method (& self ) -> String ; }
# impl Foo for u8 { fn method (& self ) -> String { format! ( "u8: {}" , *
self )}}
# impl Foo for String { fn method (& self ) -> String { format! ( "string:
{}" , * self )}}
fn main () {
let x = "Hello" .to_string ();
do_something (& x);
}
A function that receives a trait object is not specialized for each of the types
that Foo implements : only one copy is generated, sometimes (but not
always) resulting in less code inflation. However, dynamic dispatch comes at
the cost of requiring the slowest calls to virtual functions, effectively
inhibiting any possibility of online insertion and related optimizations.
Why pointers?
Rust, unlike many managed languages, doesn't put things behind default
pointers, which results in types having different sizes. Knowing the size of a
value at compile time is important for things like passing it as an argument to
a function, moving it on the stack, and allocating (and de-allocating) space
for it in the mound for storage.
For Foo , we would need to have a value that could be smaller than a String
(24 bytes) or a u8 (1 byte), as well as any other type that Foo can implement
in dependent crates (any number of bytes). There is no way to guarantee that
the latter case can work if the values are not stored in a pointer, since those
other types can be of arbitrary size.
Placing the value behind a pointer means that the size of the value is not
relevant when we are throwing a trait object around, just the size of the
pointer itself.
Representation
Trait methods can be called on a trait object through a function pointer
register traditionally called 'vtable' (created and managed by the compiler).
Trait objects are simple and complex at the same time: their representation
and distribution is quite straightforward, but there are some rather rare error
messages and some surprising behaviors to discover.
Let's start with the simplest, the representation of a trait object at runtime.
The std :: raw module contains structs with distributions that are just as
complicated as those of built-in types, including trait objects :
# mod foo {
pub struct TraitObject {
pub data: * mut (),
pub vtable: * mut (),
}
#}
That is, a trait object like & Foo consists of a 'data' pointer and a 'vtable'
pointer.
The data pointer points to the data (of an unknown type T ) stored by the trait
object, and the vtable pointer points to the vtable (table of virtual methods')
('virtual method table') corresponding to the implementation of Foo for T .
A vtable is essentially a struct of function pointers, pointing to the particular
segment of machine code for each method implementation. A call to method
like object_trait.method () will return the correct pointer from the vtable and then
make a dynamic call of it. For example:
struct FooVtable {
destroyer: fn (* mut ()),
size: usize,
alignment: usize,
method: fn (* const ()) -> String,
}
// u8:
byte.metodo ()
}
// String:
string.metodo ()
}
// b.metodo ();
(b.vtable.metodo) (b.data);
// and.method ();
(y.vtable.metodo) (y.data);
Object Security
Not every trait can be used to create a trait object. For example, vectors
implement Clone , but if we try to create a trait object:
let v = vec! [1, 2, 3];
let o = & v as & Clone;
We get an error:
error: cannot convert to a trait object because trait `core :: clone ::
Clone` is not object-safe [E0038]
let o = & v as & Clone;
^~
note: the trait cannot require that `Self: Sized`
let o = & v as & Clone;
^~
The error says that Clone is not 'object-safe'. Only traits that are safe for
objects can be used in creating trait objects. A trait is safe for objects if both
conditions are true:
the trait does not require Self: Sized
all its methods are safe for objects
So what makes a method safe for objects? Each method should require that
Self: Sized or all of the following:
must not have any type parameters
you shouldn't use Self
Uff! As we can see, almost all of these rules talk about Self . A good intuition
would be "except for special circumstances, if your trait method uses Self , it
is not safe for objects."
Closures
Sometimes it is useful to wrap a function and its free variables for better
clarity and reusability. The free variables that can be used come from the
outside scope and are 'closed over' when used in the function. Hence the
name 'closure'. Rust provides a very good implementation , as we will see
below.
Syntax
The closures look like this:
let sum_one = | x: i32 | x + 1 ;
result + = 1 ;
result + = 1 ;
Outcome
};
Closures move
We can force our closure to take belong to its environment with the move
keyword :
let num = 5 ;
{
let mut sum_num = | x: i32 | num + = x;
sum_num ( 5 );
}
assert_eq! ( 10 , num);
In this case, our closure took a mutable reference to num , and when we call
sum_num , it mutes the underlying value, just as we expected. We also need to
declare sum_num as mut , since we are mutating its environment.
If we change it to a closure move , it is different:
let mut num = 5 ;
{
let mut sum_num = move | x: i32 | num + = x;
sum_num ( 5 );
}
assert_eq! ( 5 , num);
We get only 5 . Instead of taking a mutable loan on our number , we take
property on a copy.
Another way to think about closures move is: they provide the closure with its
own activation record. Without move , the closure can be associated with the
activation record that created it, while a closure move is self-contained.
Which means, for example, that you generally cannot return a non- move
closure from a function.
But before talking about receiving closures as parameters and using them as
return values, we should talk a little more about their implementation. Like a
Rust system programming language it gives you a ton of control over what
your code does, and closures are no different.
Implementation of Closures
Rust's implementation of closures is a little different than other languages. In
Rust, closures are effectively an alternate syntax for traits. Before continuing,
you will need to have read the chapter on traits as well as the chapter on trait
objects .
Have you already read them? Excellent.
The key to understanding how closures work is kind of weird: Using () to
call a function, like foo () , is an overload operator. Starting from this premise,
everything else fits. At Rust we make use of the traits system to overload
operators. Calling functions is no different. There are three traits that we can
overload:
# mod foo {
pub trait Fn <Args>: FnMut <Args> {
extern "rust-call" fn call (& self, args: Args) -> Self :: Output;
}
some_closure ( 1 )
}
assert_eq! ( 3 , answer);
We pass our closure, |x|x+2 , to call_with_one . calling_with_one does what it
suggests: calls the closure, giving it 1 as an argument.
Let's examine the call_with_one signature in more detail:
fn call_with_one <F> (some_closure: F) -> i32
# where F: Fn ( i32 ) -> i32 {
# some_closure ( 1 )}
We receive a parameter of type F . We also return an i32 . This part is not
interesting. The following is:
# fn call_with_one <F> (some_closure: F) -> i32
where F: Fn ( i32 ) -> i32 {
# some_closure ( 1 )}
Because Fn is a trait, we can limit our generic with it. In this case, our
closure receives an i32 and returns an i32 , that is why the generic limit we
use is Fn (i32) -> i32 .
There is another key point here: because we are limiting a generic with a trait,
the call will be monomorphized, and consequently, we will be doing static
dispatch in the closure. That is super cool. In many languages, closures are
inherently assigned from the mound, and will almost always involve dynamic
dispatch. In Rust we can assign the environment of our closures from the
stack, as well as dispatch the call statically. This occurs quite frequently with
iterators and their adapters, which receive closures as arguments.
Of course, if we want dynamic dispatch, we can have it too. A trait object, as
usual, handles this case:
fn call_with_one (some_closure: & Fn ( i32 ) -> i32 ) -> i32 {
some_closure ( 1 )
}
assert_eq! ( 3 , answer);
We now receive a trait object, an & Fn . And we have to make a reference to
our closure when we pass it to call_with_one , that is why we use & || .
Function pointers and closures
A function pointer is a kind of closure that has no environment. As a
consequence, we can pass a function pointer to any function that receives a
closure as an argument:
fn call_with_one (some_closure: & Fn ( i32 ) -> i32 ) -> i32 {
some_closure ( 1 )
}
let f = sum_one;
assert_eq! ( 2 , answer);
In the previous example we don't need the intermediate variable f strictly, the
function name also works:
Returning closures
It is very common for functional styled code to return closures in various
situations. If you try to return a closure, you could make an error. At first it
may seem strange, but later we will understand. You would probably try to
return a closure from a function like this:
fn factory () -> (Fn (i32) -> i32) {
let num = 5;
| x | x + num
}
| x | x + num
}
| x | x + num
}
let answer = f ( 1 );
assert_eq! ( 6 , answer);
#}