Comprehensive Rust
Comprehensive Rust
html
This is a three day Rust course developed by the Android team. The course covers the full
spectrum of Rust, from basic syntax to advanced topics like generics and error handling. It
also includes Android-specific content on the last day.
The goal of the course is to teach you Rust. We assume you don’t know anything about Rust
and hope to:
The first three days show you the fundamentals of Rust. Following this, you’re invited to dive
into one or more specialized topics:
• Android: a half-day course on using Rust for Android platform development (AOSP).
This includes interoperability with C, C++, and Java.
• Bare-metal: a whole-day class on using Rust for bare-metal (embedded) development.
Both microcontrollers and application processors are covered.
• Concurrency: a whole-day class on concurrency in Rust. We cover both classical
concurrency (preemptively scheduling using threads and mutexes) and async/await
concurrency (cooperative multitasking using futures).
Non-Goals
Rust is a large language and we won’t be able to cover all of it in a few days. Some non-goals
of this course are:
• Learning how to develop macros: please see Chapter 19.5 in the Rust Book and Rust by
Example instead.
Assumptions
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
The course
traffic. assumes
Learn more thatgot
OK, youit already know how to program. Rust is a statically- typed
language and we will sometimes make comparisons with C and C++ to better explain or
contrast the Rust approach.
▸ Speaker Notes
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
Here is a bit of background information about how we’ve been running the course internally
at Google.
1. Make yourself familiar with the course material. We’ve included speaker notes to help
highlight the key points (please help us by contributing more speaker notes!). When
presenting, you should make sure to open the speaker notes in a popup (click the link
with a little arrow next to “Speaker Notes”). This way you have a clean screen to
present to the class.
2. Decide on the dates. Since the course takes at least three full days, we recommend
that you schedule the days over two weeks. Course participants have said that they
find it helpful to have a gap in the course since it helps them process all the
information we give them.
3. Find a room large enough for your in-person participants. We recommend a class size
of 15-25 people. That’s small enough that people are comfortable asking questions —
it’s also small enough that one instructor will have time to answer the questions. Make
sure the room has desks for yourself and for the students: you will all need to be able
to sit and work with your laptops. In particular, you will be doing a lot of live-coding as
an instructor, so a lectern won’t be very helpful for you.
4. On the day of your course, show up to the room a little early to set things up. We
recommend presenting directly using mdbook serve running on your laptop (see the
installation instructions). This ensures optimal performance with no lag as you change
pages. Using your laptop will also allow you to fix typos as you or the course
participants spot them.
5. Let people solve the exercises by themselves or in small groups. We typically spend
30-45 minutes on exercises in the morning and in the afternoon (including time to
review the solutions). Make sure to ask people if they’re stuck or if there is anything
you can help with. When you see that several people have the same problem, call it out
to the class and offer a solution, e.g., by showing people where to find the relevant
information in the standard library.
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
That is all,
traffic. good
Learn luck OK,
more running
got it the course! We hope it will be as much fun for you as it has
Please provide feedback afterwards so that we can keep improving the course. We would
love to hear what worked well for you and what can be made better. Your students are also
very welcome to send us feedback!
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
Course Structure
Deep Dives
In addition to the 3-day class on Rust Fundamentals, we cover some more specialized topics:
Android
The Android Deep Dive is a half-day course on using Rust for Android platform
development. This includes interoperability with C, C++, and Java.
You will need an AOSP checkout. Make a checkout of the course repository on the same
machine and move the src/android/ directory into the root of your AOSP checkout. This
will ensure that the Android build system sees the Android.bp files in src/android/ .
Ensure that adb sync works with your emulator or real device and pre-build all Android
examples using src/android/build_all.sh . Read the script to see the commands it runs
and make sure they work when you run them by hand.
Bare-Metal
The Bare-Metal Deep Dive: a full day class on using Rust for bare-metal (embedded)
development. Both microcontrollers and application processors are covered.
For the microcontroller part, you will need to buy the BBC micro:bit v2 development board
ahead of time. Everybody will need to install a number of packages as described on the
welcome
This page.
site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
Concurrency
The Concurrency Deep Dive is a full day class on classical as well as async / await
concurrency.
You will need a fresh crate set up and the dependencies downloaded and ready to go. You
can then copy/paste the examples into src/main.rs to experiment with them:
Format
The course is meant to be very interactive and we recommend letting the questions drive
the exploration of Rust!
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
Keyboard Shortcuts
There are several useful keyboard shortcuts in mdBook:
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
Translations
The course has been translated into other languages by a set of wonderful volunteers:
Use the language picker in the top-right corner to switch between languages.
Incomplete Translations
There is a large number of in-progress translations. We link to the most recently updated
translations:
• Bengali by @raselmandol.
• French by @KookaS and @vcaen.
• German by @Throvn and @ronaldfw.
• Japanese by @CoinEZ-JPN and @momotaro1105.
If you want to help with this effort, please see our instructions for how to get going.
Translations are coordinated on the issue tracker.
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
Using Cargo
When you start reading about Rust, you will soon meet Cargo, the standard tool used in the
Rust ecosystem to build and run Rust applications. Here we want to give a brief overview of
what Cargo is and how it fits into the wider ecosystem and how it fits into this training.
Installation
This will give you the Cargo build tool ( cargo ) and the Rust compiler ( rustc ). You will also
get rustup , a command line utility that you can use to install/switch toolchains, setup cross
compilation, etc.
▸ Details
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
• rustc : the Rust compiler which turns .rs files into binaries and other intermediate
formats.
• cargo : the Rust dependency manager and build tool. Cargo knows how to download
dependencies, usually hosted on https://crates.io, and it will pass them to rustc when
building your project. Cargo also comes with a built-in test runner which is used to
execute unit tests.
• rustup : the Rust toolchain installer and updater. This tool is used to install and update
rustc and cargo when new versions of Rust is released. In addition, rustup can also
download documentation for the standard library. You can have multiple versions of
Rust installed at once and rustup will let you switch between them as needed.
▸ Details
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
Installing Cargo is still encouraged: it will make it easier for you to do the exercises. On the
last day, we will do a larger exercise which shows you how to work with dependencies and
for that you need Cargo.
1 fn main() {
2 println!("Edit me!");
3 }
You can use Ctrl + Enter to execute the code when focus is in the text box.
▸ Details
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
% rustc --version
rustc 1.69.0 (84c898d65 2023-04-16)
% cargo --version
cargo 1.69.0 (6e9a83356 2023-04-12)
With this in place, follow these steps to build a Rust binary from one of the examples in this
training:
1. Click the “Copy to clipboard” button on the example you want to copy.
2. Use cargo new exercise to create a new exercise/ directory for your code:
3. Navigate into exercise/ and use cargo run to build and run your binary:
$ cd exercise
$ cargo run
Compiling exercise v0.1.0 (/home/mgeisler/tmp/exercise)
Finished dev [unoptimized + debuginfo] target(s) in 0.75s
Running `target/debug/exercise`
Hello, world!
4. Replace the boiler-plate code in src/main.rs with your own code. For example, using
the example on the previous page, make src/main.rs look like
fn main() {
println!("Edit me!");
}
$ cargo run
Compiling exercise v0.1.0 (/home/mgeisler/tmp/exercise)
Finished dev [unoptimized + debuginfo] target(s) in 0.24s
Running `target/debug/exercise`
Edit me!
6. Use cargo check to quickly check your project for errors, use cargo build to compile
it without running it. You will find the output in target/debug/ for a normal debug
build. Use cargo build --release to produce an optimized release build in
target/release/ .
7. You can add dependencies for your project by editing Cargo.toml . When you run
cargo commands, it will automatically download and compile missing dependencies
for you.
▸ Details
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
Welcome to Day 1
This is the first day of Comprehensive Rust. We will cover a lot of ground today:
• Basic Rust syntax: variables, scalar and compound types, enums, structs, references,
functions, and methods.
▸ Details
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
What is Rust?
Rust is a new programming language which had its 1.0 release in 2015:
▸ Details
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
Hello World!
Let us jump into the simplest possible Rust program, a classic Hello World program:
1 fn main() {
2 println!("Hello �!");
3 }
▸ Details
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
Small Example
Here is a small example program in Rust:
▸ Details
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
Why Rust?
Some unique selling points of Rust:
▸ Details
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
• No uninitialized variables.
• No memory leaks (mostly, see notes).
• No double-frees.
• No use-after-free.
• No NULL pointers.
• No forgotten locked mutexes.
• No data races between threads.
• No iterator invalidation.
▸ Details
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
Runtime Guarantees
No undefined behavior at runtime:
▸ Details
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
Modern Features
Rust is built with all the experience gained in the last decades.
Language Features
• Enums and pattern matching.
• Generics.
• No overhead FFI.
• Zero-cost abstractions.
Tooling
• Great compiler errors.
• Built-in dependency manager.
• Built-in support for testing.
• Excellent Language Server Protocol support.
▸ Details
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
Basic Syntax
Much of the Rust syntax will be familiar to you from C, C++ or Java:
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
Scalar Types
Types Literals
i8 , i16 , i32 , i64 , i128 , -10 , 0 , 1_000 ,
Signed integers
isize 123_i64
Unicode scalar
char 'a' , 'α' , '∞'
values
Booleans bool true , false
▸ Details
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
Compound Types
Types Literals
Arrays [T; N] [20, 30, 40] , [0; 3]
1 fn main() {
2 let mut a: [i8; 10] = [42; 10];
3 a[5] = 0;
4 println!("a: {:?}", a);
5 }
1 fn main() {
2 let t: (i8, bool) = (7, true);
3 println!("1st index: {}", t.0);
4 println!("2nd index: {}", t.1);
5 }
▸ Details
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
References
Like C++, Rust has references:
1 fn main() {
2 let mut x: i32 = 10;
3 let ref_x: &mut i32 = &mut x;
4 *ref_x = 20;
5 println!("x: {x}");
6 }
Some notes:
• We must dereference ref_x when assigning to it, similar to C and C++ pointers.
• Rust will auto-dereference in some cases, in particular when invoking methods (try
ref_x.count_ones() ).
• References that are declared as mut can be bound to different values over their
lifetime.
▸ Details
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
Dangling References
Rust will statically forbid dangling references:
1 fn main() {
2 let ref_x: &i32;
3 {
4 let x: i32 = 10;
5 ref_x = &x;
6 }
7 println!("ref_x: {ref_x}");
8 }
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
Slices
A slice gives you a view into a larger collection:
1 fn main() {
2 let mut a: [i32; 6] = [10, 20, 30, 40, 50, 60];
3 println!("a: {a:?}");
4
5 let s: &[i32] = &a[2..4];
6
7 println!("s: {s:?}");
8 }
▸ Details
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
String vs str
We can now understand the two string types in Rust:
1 fn main() {
2 let s1: &str = "World";
3 println!("s1: {s1}");
4
5 let mut s2: String = String::from("Hello ");
6 println!("s2: {s2}");
7 s2.push_str(s1);
8 println!("s2: {s2}");
9
10 let s3: &str = &s2[6..];
11 println!("s3: {s3}");
12 }
Rust terminology:
▸ Details
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
Functions
A Rust version of the famous FizzBuzz interview question:
1 fn main() {
2 print_fizzbuzz_to(20);
3 }
4
5 fn is_divisible(n: u32, divisor: u32) -> bool {
6 if divisor == 0 {
7 return false;
8 }
9 n % divisor == 0
10 }
11
12 fn fizzbuzz(n: u32) -> String {
13 let fizz = if is_divisible(n, 3) { "fizz" } else { "" };
14 let buzz = if is_divisible(n, 5) { "buzz" } else { "" };
15 if fizz.is_empty() && buzz.is_empty() {
16 return format!("{n}");
17 }
18 format!("{fizz}{buzz}")
19 }
20
21 fn print_fizzbuzz_to(n: u32) {
22 for i in 1..=n {
23 println!("{}", fizzbuzz(i));
24 }
25 }
▸ Details
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
Rustdoc
All language items in Rust can be documented using special /// syntax.
1 /// Determine whether the first argument is divisible by the second argument.
2 ///
3 /// If the second argument is zero, the result is false.
4 fn is_divisible_by(lhs: u32, rhs: u32) -> bool {
5 if rhs == 0 {
6 return false; // Corner case, early return
7 }
8 lhs % rhs == 0 // The last expression in a block is the return value
9 }
The contents are treated as Markdown. All published Rust library crates are automatically
documented at docs.rs using the rustdoc tool. It is idiomatic to document all public items
in an API using this pattern.
▸ Details
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
Methods
Methods are functions associated with a type. The self argument of a method is an
instance of the type it is associated with:
1 struct Rectangle {
2 width: u32,
3 height: u32,
4 }
5
6 impl Rectangle {
7 fn area(&self) -> u32 {
8 self.width * self.height
9 }
10
11 fn inc_width(&mut self, delta: u32) {
12 self.width += delta;
13 }
14 }
15
16 fn main() {
17 let mut rect = Rectangle { width: 10, height: 5 };
18 println!("old area: {}", rect.area());
19 rect.inc_width(5);
20 println!("new area: {}", rect.area());
21 }
• We will look much more at methods in today’s exercise and in tomorrow’s class.
▸ Details
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
Function Overloading
Overloading is not supported:
1 fn pick_one<T>(a: T, b: T) -> T {
2 if std::process::id() % 2 == 0 { a } else { b }
3 }
4
5 fn main() {
6 println!("coin toss: {}", pick_one("heads", "tails"));
7 println!("cash prize: {}", pick_one(500, 1000));
8 }
▸ Details
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
▸ Details
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
Implicit Conversions
Rust will not automatically apply implicit conversions between types (unlike C++). You can see
this in a program like this:
The Rust integer types all implement the From<T> and Into<T> traits to let us convert
between them. The From<T> trait has a single from() method and similarly, the Into<T>
trait has a single into() method. Implementing these traits is how a type expresses that it
can be converted into another type.
The standard library has an implementation of From<i8> for i16 , which means that we
can convert a variable x of type i8 to an i16 by calling i16::from(x) . Or, simpler, with
x.into() , because From<i8> for i16 implementation automatically create an
implementation of Into<i16> for i8 .
The same applies for your own From implementations for your own types, so it is sufficient
to only implement From to get a respective Into implementation automatically.
3. Change the types of x and y to other things (such as f32 , bool , i128 ) to see which
types you can convert to which other types. Try converting small types to big types and
the other way around. Check the standard library documentation to see if From<T> is
implemented for the pairs you check.
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
You can print such an array by asking for its debug representation with {:?} :
1 fn main() {
2 let array = [10, 20, 30];
3 println!("array: {array:?}");
4 }
Rust lets you iterate over things like arrays and ranges using the for keyword:
1 fn main() {
2 let array = [10, 20, 30];
3 print!("Iterating over array:");
4 for n in array {
5 print!(" {n}");
6 }
7 println!();
8
9 print!("Iterating over range:");
10 for i in 0..3 {
11 print!(" {}", array[i]);
12 }
13 println!();
14 }
Use the above to write a function pretty_print which pretty-print a matrix and a function
transpose which will transpose a matrix (turn rows into columns):
⎛ ⎡ 1 2 3⎤ ⎞ ⎡ 1 4 7⎤
transpose ⎜ ⎢ 4 5 6⎥ ⎟ == ⎢ 2 5 8⎥
⎝ ⎣ 7 8 9⎦ ⎠ ⎣ 3 6 9⎦
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
fn main() {
let matrix = [
[101, 102, 103], // <-- the comment makes rustfmt add a newline
[201, 202, 203],
[301, 302, 303],
];
println!("matrix:");
pretty_print(&matrix);
Bonus Question
Could you use &[i32] slices instead of hard-coded 3 × 3 matrices for your argument and
return types? Something like &[&[i32]] for a two-dimensional slice-of-slices. Why or why
not?
▸ Details
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
Variables
Rust provides type safety via static typing. Variable bindings are immutable by default:
1 fn main() {
2 let x: i32 = 10;
3 println!("x: {x}");
4 // x = 20;
5 // println!("x: {x}");
6 }
▸ Details
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
Type Inference
Rust will look at how the variable is used to determine the type:
1 fn takes_u32(x: u32) {
2 println!("u32: {x}");
3 }
4
5 fn takes_i8(y: i8) {
6 println!("i8: {y}");
7 }
8
9 fn main() {
10 let x = 10;
11 let y = 20;
12
13 takes_u32(x);
14 takes_i8(y);
15 // takes_u32(y);
16 }
▸ Details
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
const
You can declare compile-time constants:
According to the Rust RFC Book these are inlined upon use.
static
You can also declare static variables:
As noted in the Rust RFC Book, these are not inlined upon use and have an actual associated
memory location. This is useful for unsafe and embedded code, and the variable lives
through the entirety of the program execution. When a globally-scoped value does not have
a reason to need object identity, const is generally preferred.
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
Because static variables are accessible from any thread, they need to be guarded, for
traffic. Learn more OK, got it
example by a Mutex , or accessed using unsafe code. We will look at mutating static data in
the chapter on Unsafe Rust.
▸ Details
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
1 fn main() {
2 let a = 10;
3 println!("before: {a}");
4
5 {
6 let a = "hello";
7 println!("inner scope: {a}");
8
9 let a = true;
10 println!("shadowed in inner scope: {a}");
11 }
12
13 println!("after: {a}");
14 }
▸ Details
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
Memory Management
Traditionally, languages have fallen into two broad categories:
Full control and safety via compile time enforcement of correct memory management.
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
1 fn main() {
2 let s1 = String::from("Hello");
3 }
Stack Heap
s1
ptr H e l l o
len 5
capacity 5
▸ Details
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
If not done with care, this can lead to crashes, bugs, security vulnerabilities, and memory
leaks.
C Example
You must call free on every pointer you allocate with malloc :
void foo(size_t n) {
int* int_array = malloc(n * sizeof(int));
//
// ... lots of code
//
free(int_array);
}
Memory is leaked if the function returns early between malloc and free : the pointer is lost
and we cannot deallocate the memory. Worse, freeing the pointer twice, or accessing a freed
pointer can lead to exploitable security vulnerabilities.
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
By wrapping a pointer in an object, you can free memory when the object is destroyed. The
compiler guarantees that this happens, even if an exception is raised.
This is often called resource acquisition is initialization (RAII) and gives you smart pointers.
C++ Example
• The std::unique_ptr object is allocated on the stack, and points to memory allocated
on the heap.
• At the end of say_hello , the std::unique_ptr destructor will run.
• The destructor frees the Person object it points to.
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
Java Example
The person object is not deallocated after sayHello returns:
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
▸ Details
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
Comparison
Here is a rough comparison of the memory management techniques.
Ownership
All variable bindings have a scope where they are valid and it is an error to use a variable
outside its scope:
• At the end of the scope, the variable is dropped and the data is freed.
• A destructor can run here to free up resources.
• We say that the variable owns the value.
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
Move Semantics
An assignment will transfer ownership between variables:
1 fn main() {
2 let s1: String = String::from("Hello!");
3 let s2: String = s1;
4 println!("s2: {s2}");
5 // println!("s1: {s1}");
6 }
▸ Details
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
Before move to s2 :
Stack Heap
s1
ptr R u s t
len 4
capacity 4
After move to s2 :
Stack Heap
s1 (inaccessible)
ptr R u s t
len 4
capacity 4
s2
ptr
len 4
capacity 4
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
std::string s1 = "Cpp";
std::string s2 = s1; // Duplicate the data in s1.
• The heap data from s1 is duplicated and s2 gets its own independent copy.
• When s1 and s2 go out of scope, they each free their own memory.
Before copy-assignment:
Stack Heap
s1
ptr C p p
len 3
capacity 3
After copy-assignment:
Stack Heap
s1
ptr C p p
len 3
capacity 3
s2
ptr C p p
len 3
capacity 3
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
1 fn say_hello(name: String) {
2 println!("Hello {name}")
3 }
4
5 fn main() {
6 let name = String::from("Alice");
7 say_hello(name);
8 // say_hello(name);
9 }
▸ Details
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
1 fn main() {
2 let x = 42;
3 let y = x;
4 println!("x: {x}");
5 println!("y: {y}");
6 }
▸ Details
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
Borrowing
Instead of transferring ownership when calling a function, you can let a function borrow the
value:
1 #[derive(Debug)]
2 struct Point(i32, i32);
3
4 fn add(p1: &Point, p2: &Point) -> Point {
5 Point(p1.0 + p2.0, p1.1 + p2.1)
6 }
7
8 fn main() {
9 let p1 = Point(3, 4);
10 let p2 = Point(10, 20);
11 let p3 = add(&p1, &p2);
12 println!("{p1:?} + {p2:?} = {p3:?}");
13 }
• The add function borrows two points and returns a new point.
• The caller retains ownership of the inputs.
▸ Details
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
• You can have one or more &T values at any given time, or
• You can have exactly one &mut T value.
1 fn main() {
2 let mut a: i32 = 10;
3 let b: &i32 = &a;
4
5 {
6 let c: &mut i32 = &mut a;
7 *c = 20;
8 }
9
10 println!("a: {a}");
11 println!("b: {b}");
12 }
▸ Details
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
Lifetimes
A borrowed value has a lifetime:
• The lifetime can be implicit: add(p1: &Point, p2: &Point) -> Point .
• Lifetimes can also be explicit: &'a Point , &'document str .
• Read &'a Point as “a borrowed Point which is valid for at least the lifetime a ”.
• Lifetimes are always inferred by the compiler: you cannot assign a lifetime yourself.
◦ Lifetime annotations create constraints; the compiler verifies that there is a valid
solution.
• Lifetimes for function arguments and return values must be fully specified, but Rust
allows lifetimes to be elided in most cases with a few simple rules.
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
1 #[derive(Debug)]
2 struct Point(i32, i32);
3
4 fn left_most<'a>(p1: &'a Point, p2: &'a Point) -> &'a Point {
5 if p1.0 < p2.0 { p1 } else { p2 }
6 }
7
8 fn main() {
9 let p1: Point = Point(10, 10);
10 let p2: Point = Point(20, 20);
11 let p3: &Point = left_most(&p1, &p2);
12 println!("left-most point: {:?}", p3);
13 }
▸ Details
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
1 #[derive(Debug)]
2 struct Highlight<'doc>(&'doc str);
3
4 fn erase(text: String) {
5 println!("Bye {text}!");
6 }
7
8 fn main() {
9 let text = String::from("The quick brown fox jumps over the lazy dog.");
10 let fox = Highlight(&text[4..19]);
11 let dog = Highlight(&text[35..43]);
12 // erase(text);
13 println!("{fox:?}");
14 println!("{dog:?}");
15 }
▸ Details
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
▸ Details
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
Storing Books
We will learn much more about structs and the Vec<T> type tomorrow. For now, you just
need to know part of its API:
1 fn main() {
2 let mut vec = vec![10, 20];
3 vec.push(30);
4 let midpoint = vec.len() / 2;
5 println!("middle value: {}", vec[midpoint]);
6 for item in &vec {
7 println!("item: {item}");
8 }
9 }
Use this to model a library’s book collection. Copy the code below to https://play.rust-
lang.org/ and update the types to make it compile:
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
struct Library {
books: Vec<Book>,
}
struct Book {
title: String,
year: u16,
}
impl Book {
// This is a constructor, used below.
fn new(title: &str, year: u16) -> Book {
Book {
title: String::from(title),
year,
}
}
}
//fn print_books(self) {
// todo!("Iterate over `self.books` and each book's title and year")
//}
▸ Details
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
Iterator
Traits are like interfaces: they describe behavior (methods) for a type. The Iterator trait
simply says that you can call next until you get None back:
1 fn main() {
2 let v: Vec<i8> = vec![10, 20, 30];
3 let mut iter = v.iter();
4
5 println!("v[0]: {:?}", iter.next());
6 println!("v[1]: {:?}", iter.next());
7 println!("v[2]: {:?}", iter.next());
8 println!("No more items: {:?}", iter.next());
9 }
What is the type returned by the iterator? Test your answer here:
1 fn main() {
2 let v: Vec<i8> = vec![10, 20, 30];
3 let mut iter = v.iter();
4
5 let v0: Option<..> = iter.next();
6 println!("v0: {v0:?}");
7 }
IntoIterator
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
The Iterator trait tells you how to iterate once you have created an iterator. The related
The syntax here means that every implementation of IntoIterator must declare two types:
Note that IntoIter and Item are linked: the iterator must have the same Item type, which
means that it returns Option<Item>
1 fn main() {
2 let v: Vec<String> = vec![String::from("foo"), String::from("bar")];
3 let mut iter = v.into_iter();
4
5 let v0: Option<..> = iter.next();
6 println!("v0: {v0:?}");
7 }
for Loops
Now that we know both Iterator and IntoIterator , we can build for loops. They call
into_iter() on an expression and iterates over the resulting iterator:
1 fn main() {
2 let v: Vec<String> = vec![String::from("foo"), String::from("bar")];
3
4 for word in &v {
5 println!("word: {word}");
6 }
7
8 for word in v {
9 println!("word: {word}");
10 }
11 }
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
What is the type of word in each loop?
traffic. Learn more OK, got it
Experiment with the code above and then consult the documentation for impl
IntoIterator for &Vec<T> and impl IntoIterator for Vec<T> to check your answers.
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
Welcome to Day 2
Now that we have seen a fair amount of Rust, we will continue with:
• Control flow constructs: if , if let , while , while let , break , and continue .
• The Standard Library: String , Option and Result , Vec , HashMap , Rc and Arc .
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
Structs
Like C and C++, Rust has support for custom structs:
1 struct Person {
2 name: String,
3 age: u8,
4 }
5
6 fn main() {
7 let mut peter = Person {
8 name: String::from("Peter"),
9 age: 27,
10 };
11 println!("{} is {} years old", peter.name, peter.age);
12
13 peter.age = 28;
14 println!("{} is {} years old", peter.name, peter.age);
15
16 let jackie = Person {
17 name: String::from("Jackie"),
18 ..peter
19 };
20 println!("{} is {} years old", jackie.name, jackie.age);
21 }
▸ Details
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
Tuple Structs
If the field names are unimportant, you can use a tuple struct:
1 struct PoundsOfForce(f64);
2 struct Newtons(f64);
3
4 fn compute_thruster_force() -> PoundsOfForce {
5 todo!("Ask a rocket scientist at NASA")
6 }
7
8 fn set_thruster_force(force: Newtons) {
9 // ...
10 }
11
12 fn main() {
13 let force = compute_thruster_force();
14 set_thruster_force(force);
15 }
16
▸ Details
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
1 #[derive(Debug)]
2 struct Person {
3 name: String,
4 age: u8,
5 }
6
7 impl Person {
8 fn new(name: String, age: u8) -> Person {
9 Person { name, age }
10 }
11 }
12
13 fn main() {
14 let peter = Person::new(String::from("Peter"), 27);
15 println!("{peter:?}");
16 }
▸ Details
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
Enums
The enum keyword allows the creation of a type which has a few different variants:
▸ Details
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
Variant Payloads
You can define richer enums where the variants carry data. You can then use the match
statement to extract the data from each variant:
1 enum WebEvent {
2 PageLoad, // Variant without payload
3 KeyPress(char), // Tuple struct variant
4 Click { x: i64, y: i64 }, // Full struct variant
5 }
6
7 #[rustfmt::skip]
8 fn inspect(event: WebEvent) {
9 match event {
10 WebEvent::PageLoad => println!("page loaded"),
11 WebEvent::KeyPress(c) => println!("pressed '{c}'"),
12 WebEvent::Click { x, y } => println!("clicked at x={x}, y={y}"),
13 }
14 }
15
16 fn main() {
17 let load = WebEvent::PageLoad;
18 let press = WebEvent::KeyPress('x');
19 let click = WebEvent::Click { x: 20, y: 80 };
20
21 inspect(load);
22 inspect(press);
23 inspect(click);
24 }
▸ Details
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
Enum Sizes
Rust enums are packed tightly, taking constraints due to alignment into account:
1 use std::any::type_name;
2 use std::mem::{align_of, size_of};
3
4 fn dbg_size<T>() {
5 println!("{}: size {} bytes, align: {} bytes",
6 type_name::<T>(), size_of::<T>(), align_of::<T>());
7 }
8
9 enum Foo {
10 A,
11 B,
12 }
13
14 fn main() {
15 dbg_size::<Foo>();
16 }
▸ Details
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
Methods
Rust allows you to associate functions with your new types. You do this with an impl block:
1 #[derive(Debug)]
2 struct Person {
3 name: String,
4 age: u8,
5 }
6
7 impl Person {
8 fn say_hello(&self) {
9 println!("Hello, my name is {}", self.name);
10 }
11 }
12
13 fn main() {
14 let peter = Person {
15 name: String::from("Peter"),
16 age: 27,
17 };
18 peter.say_hello();
19 }
▸ Details
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
Method Receiver
The &self above indicates that the method borrows the object immutably. There are other
possible receivers for a method:
• &self : borrows the object from the caller using a shared and immutable reference.
The object can be used again afterwards.
• &mut self : borrows the object from the caller using a unique and mutable reference.
The object can be used again afterwards.
• self : takes ownership of the object and moves it away from the caller. The method
becomes the owner of the object. The object will be dropped (deallocated) when the
method returns, unless its ownership is explicitly transmitted. Complete ownership
does not automatically mean mutability.
• mut self : same as above, but the method can mutate the object.
• No receiver: this becomes a static method on the struct. Typically used to create
constructors which are called new by convention.
Beyond variants on self , there are also special wrapper types allowed to be receiver types,
such as Box<Self> .
▸ Details
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
Example
1 #[derive(Debug)]
2 struct Race {
3 name: String,
4 laps: Vec<i32>,
5 }
6
7 impl Race {
8 fn new(name: &str) -> Race { // No receiver, a static method
9 Race { name: String::from(name), laps: Vec::new() }
10 }
11
12 fn add_lap(&mut self, lap: i32) { // Exclusive borrowed read-write access to se
13 self.laps.push(lap);
14 }
15
16 fn print_laps(&self) { // Shared and read-only borrowed access to self
17 println!("Recorded {} laps for {}:", self.laps.len(), self.name);
18 for (idx, lap) in self.laps.iter().enumerate() {
19 println!("Lap {idx}: {lap} sec");
20 }
21 }
22
23 fn finish(self) { // Exclusive ownership of self
24 let total = self.laps.iter().sum::<i32>();
25 println!("Race {} is finished, total lap time: {}", self.name, total
26 }
27 }
28
29 fn main() {
30 let mut race = Race::new("Monaco Grand Prix");
31 race.add_lap(70);
32 race.add_lap(68);
33 race.print_laps();
34 race.add_lap(71);
35 race.print_laps();
36 race.finish();
37 // race.add_lap(42);
38 }
▸ Details
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
Pattern Matching
The match keyword let you match a value against one or more patterns. The comparisons
are done from top to bottom and the first match wins.
1 fn main() {
2 let input = 'x';
3
4 match input {
5 'q' => println!("Quitting"),
6 'a' | 's' | 'w' | 'd' => println!("Moving around"),
7 '0'..='9' => println!("Number input"),
8 _ => println!("Something else"),
9 }
10 }
▸ Details
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
Destructuring Enums
Patterns can also be used to bind variables to parts of your values. This is how you inspect
the structure of your types. Let us start with a simple enum type:
1 enum Result {
2 Ok(i32),
3 Err(String),
4 }
5
6 fn divide_in_two(n: i32) -> Result {
7 if n % 2 == 0 {
8 Result::Ok(n / 2)
9 } else {
10 Result::Err(format!("cannot divide {n} into two equal parts"))
11 }
12 }
13
14 fn main() {
15 let n = 100;
16 match divide_in_two(n) {
17 Result::Ok(half) => println!("{n} divided in two is {half}"),
18 Result::Err(msg) => println!("sorry, an error happened: {msg}"),
19 }
20 }
Here we have used the arms to destructure the Result value. In the first arm, half is
bound to the value inside the Ok variant. In the second arm, msg is bound to the error
message.
▸ Details
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
Destructuring Structs
You can also destructure structs :
1 struct Foo {
2 x: (u32, u32),
3 y: u32,
4 }
5
6 #[rustfmt::skip]
7 fn main() {
8 let foo = Foo { x: (1, 2), y: 3 };
9 match foo {
10 Foo { x: (1, b), y } => println!("x.0 = 1, b = {b}, y = {y}"),
11 Foo { y: 2, x: i } => println!("y = 2, x = {i:?}"),
12 Foo { y, .. } => println!("y = {y}, other fields were ignored"
13 }
14 }
▸ Details
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
Destructuring Arrays
You can destructure arrays, tuples, and slices by matching on their elements:
1 #[rustfmt::skip]
2 fn main() {
3 let triple = [0, -2, 3];
4 println!("Tell me about {triple:?}");
5 match triple {
6 [0, y, z] => println!("First is 0, y = {y}, and z = {z}"),
7 [1, ..] => println!("First is 1 and the rest were ignored"),
8 _ => println!("All elements were ignored"),
9 }
10 }
▸ Details
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
Match Guards
When matching, you can add a guard to a pattern. This is an arbitrary Boolean expression
which will be executed if the pattern matches:
1 #[rustfmt::skip]
2 fn main() {
3 let pair = (2, -2);
4 println!("Tell me about {pair:?}");
5 match pair {
6 (x, y) if x == y => println!("These are twins"),
7 (x, y) if x + y == 0 => println!("Antimatter, kaboom!"),
8 (x, _) if x % 2 == 1 => println!("The first one is odd"),
9 _ => println!("No correlation..."),
10 }
11 }
▸ Details
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
▸ Details
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
Health Statistics
You’re working on implementing a health-monitoring system. As part of that, you need to
keep track of users’ health statistics.
You’ll start with some stubbed functions in an impl block as well as a User struct
definition. Your goal is to implement the stubbed out methods on the User struct defined
in the impl block.
Copy the code below to https://play.rust-lang.org/ and fill in the missing methods:
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
impl User {
pub fn new(name: String, age: u32, height: f32) -> Self {
unimplemented!()
}
fn main() {
let bob = User::new(String::from("Bob"), 32, 155.2);
println!("I'm {} and my age is {}", bob.name(), bob.age());
}
#[test]
fn test_height() {
let bob = User::new(String::from("Bob"), 32, 155.2);
assert_eq!(bob.height(), 155.2);
}
#[test]
fn test_set_age() {
let mut bob = User::new(String::from("Bob"), 32, 155.2);
assert_eq!(bob.age(), 32);
bob.set_age(33);
assert_eq!(bob.age(), 33);
}
#[test]
fn test_visit() {
let mut bob = User::new(String::from("Bob"), 32, 155.2);
assert_eq!(bob.doctor_visits(), 0);
let report = bob.visit_doctor(Measurements {
height: 156.1,
blood_pressure: (120, 80),
});
assert_eq!(report.patient_name, "Bob");
assert_eq!(report.visit_count, 1);
assert_eq!(report.blood_pressure_change, None);
assert_eq!(report.visit_count, 2);
assert_eq!(report.blood_pressure_change, Some((-5, -4)));
}
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
Polygon Struct
We will create a Polygon struct which contain some points. Copy the code below to
https://play.rust-lang.org/ and fill in the missing methods to make the tests pass:
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
impl Point {
// add methods
}
impl Polygon {
// add methods
}
impl Circle {
// add methods
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_point_magnitude() {
let p1 = Point::new(12, 13);
assert_eq!(round_two_digits(p1.magnitude()), 17.69);
}
#[test]
fn test_point_dist() {
let p1 = Point::new(10, 10);
let p2 = Point::new(14, 13);
This site uses cookies from Google to deliver and enhance the quality5.00);
assert_eq!(round_two_digits(p1.dist(p2)), of its services and to analyze
traffic. } Learn more OK, got it
#[test]
fn test_point_add() {
let p1 = Point::new(16, 16);
let p2 = p1 + Point::new(-4, 3);
assert_eq!(p2, Point::new(12, 19));
}
#[test]
fn test_polygon_left_most_point() {
let p1 = Point::new(12, 13);
let p2 = Point::new(16, 16);
#[test]
fn test_polygon_iter() {
let p1 = Point::new(12, 13);
let p2 = Point::new(16, 16);
#[test]
fn test_shape_perimeters() {
let mut poly = Polygon::new();
poly.add_point(Point::new(12, 13));
poly.add_point(Point::new(17, 11));
poly.add_point(Point::new(16, 16));
let shapes = vec![
Shape::from(poly),
Shape::from(Circle::new(Point::new(10, 20), 5)),
];
let perimeters = shapes
.iter()
.map(Shape::perimeter)
.map(round_two_digits)
.collect::<Vec<_>>();
assert_eq!(perimeters, vec![15.48, 31.42]);
}
}
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
#[allow(dead_code)]
traffic.Learn more OK, got it
fn main() {}
▸ Details
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
Control Flow
As we have seen, if is an expression in Rust. It is used to conditionally evaluate one of two
blocks, but the blocks can have a value which then becomes the value of the if expression.
Other control flow expressions work similarly in Rust.
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
Blocks
A block in Rust contains a sequence of expressions. Each block has a value and a type, which
are those of the last expression of the block:
1 fn main() {
2 let x = {
3 let y = 10;
4 println!("y: {y}");
5 let z = {
6 let w = {
7 3 + 4
8 };
9 println!("w: {w}");
10 y * w
11 };
12 println!("z: {z}");
13 z - y
14 };
15 println!("x: {x}");
16 }
If the last expression ends with ; , then the resulting value and type is () .
The same rule is used for functions: the value of the function body is the return value:
▸ Details
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
if expressions
You use if expressions exactly like if statements in other languages:
1 fn main() {
2 let mut x = 10;
3 if x % 2 == 0 {
4 x = x / 2;
5 } else {
6 x = 3 * x + 1;
7 }
8 }
In addition, you can use if as an expression. The last expression of each block becomes
the value of the if expression:
1 fn main() {
2 let mut x = 10;
3 x = if x % 2 == 0 {
4 x / 2
5 } else {
6 3 * x + 1
7 };
8 }
▸ Details
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
if let expressions
The if let expression lets you execute different code depending on whether a value
matches a pattern:
1 fn main() {
2 let arg = std::env::args().next();
3 if let Some(value) = arg {
4 println!("Program name: {value}");
5 } else {
6 println!("Missing name?");
7 }
8 }
▸ Details
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
while loops
The while keyword works very similar to other languages:
1 fn main() {
2 let mut x = 10;
3 while x != 1 {
4 x = if x % 2 == 0 {
5 x / 2
6 } else {
7 3 * x + 1
8 };
9 }
10 println!("Final x: {x}");
11 }
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
1 fn main() {
2 let v = vec![10, 20, 30];
3 let mut iter = v.into_iter();
4
5 while let Some(x) = iter.next() {
6 println!("x: {x}");
7 }
8 }
Here the iterator returned by v.iter() will return a Option<i32> on every call to next() .
It returns Some(x) until it is done, after which it will return None . The while let lets us
keep iterating through all items.
▸ Details
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
for loops
The for loop is closely related to the while let loop. It will automatically call into_iter()
on the expression and then iterate over it:
1 fn main() {
2 let v = vec![10, 20, 30];
3
4 for x in v {
5 println!("x: {x}");
6 }
7
8 for i in (0..10).step_by(2) {
9 println!("i: {i}");
10 }
11 }
▸ Details
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
loop expressions
Finally, there is a loop keyword which creates an endless loop.
1 fn main() {
2 let mut x = 10;
3 loop {
4 x = if x % 2 == 0 {
5 x / 2
6 } else {
7 3 * x + 1
8 };
9 if x == 1 {
10 break;
11 }
12 }
13 println!("Final x: {x}");
14 }
▸ Details
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
match expressions
The match keyword is used to match a value against one or more patterns. In that sense, it
works like a series of if let expressions:
1 fn main() {
2 match std::env::args().next().as_deref() {
3 Some("cat") => println!("Will do cat things"),
4 Some("ls") => println!("Will ls some files"),
5 Some("mv") => println!("Let's move some files"),
6 Some("rm") => println!("Uh, dangerous!"),
7 None => println!("Hmm, no program name?"),
8 _ => println!("Unknown program name!"),
9 }
10 }
Like if let , each match arm must have the same type. The type is the last expression of
the block, if any. In the example above, the type is () .
▸ Details
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
Both continue and break can optionally take a label argument which is used to break out
of nested loops:
1 fn main() {
2 let v = vec![10, 20, 30];
3 let mut iter = v.into_iter();
4 'outer: while let Some(x) = iter.next() {
5 println!("x: {x}");
6 let mut i = 0;
7 while i < x {
8 println!("x: {x}, i: {i}");
9 i += 1;
10 if i == 3 {
11 break 'outer;
12 }
13 }
14 }
15 }
In this case we break the outer loop after 3 iterations of the inner loop.
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
Standard Library
Rust comes with a standard library which helps establish a set of common types used by
Rust library and programs. This way, two libraries can work together smoothly because they
both use the same String type.
• Option and Result types: used for optional values and error handling.
▸ Details
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
1 fn main() {
2 let numbers = vec![10, 20, 30];
3 let first: Option<&i8> = numbers.first();
4 println!("first: {first:?}");
5
6 let idx: Result<usize, usize> = numbers.binary_search(&10);
7 println!("idx: {idx:?}");
8 }
▸ Details
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
String
String is the standard heap-allocated growable UTF-8 string buffer:
1 fn main() {
2 let mut s1 = String::new();
3 s1.push_str("Hello");
4 println!("s1: len = {}, capacity = {}", s1.len(), s1.capacity());
5
6 let mut s2 = String::with_capacity(s1.len() + 1);
7 s2.push_str(&s1);
8 s2.push('!');
9 println!("s2: len = {}, capacity = {}", s2.len(), s2.capacity());
10
11 let s3 = String::from(" � � ");
12 println!("s3: len = {}, number of chars = {}", s3.len(),
13 s3.chars().count());
14 }
String implements Deref<Target = str> , which means that you can call all str methods
on a String .
▸ Details
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
Vec
Vec is the standard resizable heap-allocated buffer:
1 fn main() {
2 let mut v1 = Vec::new();
3 v1.push(42);
4 println!("v1: len = {}, capacity = {}", v1.len(), v1.capacity());
5
6 let mut v2 = Vec::with_capacity(v1.len() + 1);
7 v2.extend(v1.iter());
8 v2.push(9999);
9 println!("v2: len = {}, capacity = {}", v2.len(), v2.capacity());
10
11 // Canonical macro to initialize a vector with elements.
12 let mut v3 = vec![0, 0, 1, 2, 3, 4];
13
14 // Retain only the even elements.
15 v3.retain(|x| x % 2 == 0);
16 println!("{v3:?}");
17
18 // Remove consecutive duplicates.
19 v3.dedup();
20 println!("{v3:?}");
21 }
Vec implements Deref<Target = [T]> , which means that you can call slice methods on a
Vec .
▸ Details
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
HashMap
Standard hash map with protection against HashDoS attacks:
1 use std::collections::HashMap;
2
3 fn main() {
4 let mut page_counts = HashMap::new();
5 page_counts.insert("Adventures of Huckleberry Finn".to_string(), 207);
6 page_counts.insert("Grimms' Fairy Tales".to_string(), 751);
7 page_counts.insert("Pride and Prejudice".to_string(), 303);
8
9 if !page_counts.contains_key("Les Misérables") {
10 println!("We know about {} books, but not Les Misérables.",
11 page_counts.len());
12 }
13
14 for book in ["Pride and Prejudice", "Alice's Adventure in Wonderland"] {
15 match page_counts.get(book) {
16 Some(count) => println!("{book}: {count} pages"),
17 None => println!("{book} is unknown.")
18 }
19 }
20
21 // Use the .entry() method to insert a value if nothing is found.
22 for book in ["Pride and Prejudice", "Alice's Adventure in Wonderland"] {
23 let page_count: &mut i32 = page_counts.entry(book.to_string()).or_insert
24 *page_count += 1;
25 }
26
27 println!("{page_counts:#?}");
28 }
▸ Details
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
Box
Box is an owned pointer to data on the heap:
1 fn main() {
2 let five = Box::new(5);
3 println!("five: {}", *five);
4 }
Stack Heap
five
Box<T> implements Deref<Target = T> , which means that you can call methods from T
directly on a Box<T> .
▸ Details
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
1 #[derive(Debug)]
2 enum List<T> {
3 Cons(T, Box<List<T>>),
4 Nil,
5 }
6
7 fn main() {
8 let list: List<i32> = List::Cons(1, Box::new(List::Cons(2, Box::new(List::
9 println!("{list:?}");
10 }
Stack Heap
list
▸ Details
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
Niche Optimization
1 #[derive(Debug)]
2 enum List<T> {
3 Cons(T, Box<List<T>>),
4 Nil,
5 }
6
7 fn main() {
8 let list: List<i32> = List::Cons(1, Box::new(List::Cons(2, Box::new(List::
9 println!("{list:?}");
10 }
A Box cannot be empty, so the pointer is always valid and non- null . This allows the
compiler to optimize the memory layout:
Stack Heap
list
1 2 null
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
Rc
Rc is a reference-counted shared pointer. Use this when you need to refer to the same data
from multiple places:
1 use std::rc::Rc;
2
3 fn main() {
4 let mut a = Rc::new(10);
5 let mut b = Rc::clone(&a);
6
7 println!("a: {a}");
8 println!("b: {b}");
9 }
• If you need to mutate the data inside an Rc , you will need to wrap the data in a type
such as Cell or RefCell .
• See Arc and Mutex if you are in a multi-threaded context.
• You can downgrade a shared pointer into a Weak pointer to create cycles that will get
dropped.
▸ Details
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
Modules
We have seen how impl blocks let us namespace functions to a type.
1 mod foo {
2 pub fn do_something() {
3 println!("In the foo module");
4 }
5 }
6
7 mod bar {
8 pub fn do_something() {
9 println!("In the bar module");
10 }
11 }
12
13 fn main() {
14 foo::do_something();
15 bar::do_something();
16 }
▸ Details
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
Visibility
Modules are a privacy boundary:
1 mod outer {
2 fn private() {
3 println!("outer::private");
4 }
5
6 pub fn public() {
7 println!("outer::public");
8 }
9
10 mod inner {
11 fn private() {
12 println!("outer::inner::private");
13 }
14
15 pub fn public() {
16 println!("outer::inner::public");
17 super::private();
18 }
19 }
20 }
21
22 fn main() {
23 outer::public();
24 }
▸ Details
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
Paths
Paths are resolved as follows:
1. As a relative path:
2. As an absolute path:
A module can bring symbols from another module into scope with use . You will typically
see something like this at the top of each module:
1 use std::collections::HashSet;
2 use std::mem::transmute;
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
Filesystem Hierarchy
Omitting the module content will tell Rust to look for it in another file:
1 mod garden;
This tells rust that the garden module content is found at src/garden.rs . Similarly, a
garden::vegetables module can be found at src/garden/vegetables.rs .
Modules defined in files can be documented, too, using “inner doc comments”. These
document the item that contains them – in this case, a module.
1 //! This module implements the garden, including a highly performant germination
2 //! implementation.
3
4 // Re-export types from this module.
5 pub use seeds::SeedPacket;
6 pub use garden::Garden;
7
8 /// Sow the given seed packets.
9 pub fn sow(seeds: Vec<SeedPacket>) { todo!() }
10
11 /// Harvest the produce in the garden that is ready.
12 pub fn harvest(garden: &mut Garden) { todo!() }
▸ Details
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
▸ Details
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
Luhn Algorithm
The Luhn algorithm is used to validate credit card numbers. The algorithm takes a string as
input and does the following to validate the credit card number:
• Ignore all spaces. Reject number with less than two digits.
• Moving from right to left, double every second digit: for the number 1234 , we double
3 and 1 .
• After doubling a digit, sum the digits. So doubling 7 becomes 14 which becomes 5 .
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
#[test]
fn test_non_digit_cc_number() {
assert!(!luhn("foo"));
}
#[test]
fn test_empty_cc_number() {
assert!(!luhn(""));
assert!(!luhn(" "));
assert!(!luhn(" "));
assert!(!luhn(" "));
}
#[test]
fn test_single_digit_cc_number() {
assert!(!luhn("0"));
}
#[test]
fn test_two_digit_cc_number() {
assert!(luhn(" 0 0 "));
}
#[test]
fn test_valid_cc_number() {
assert!(luhn("4263 9826 4026 9299"));
assert!(luhn("4539 3195 0343 6467"));
assert!(luhn("7992 7398 713"));
}
#[test]
fn test_invalid_cc_number() {
assert!(!luhn("4223 9826 4026 9299"));
assert!(!luhn("4539 3195 0343 6476"));
assert!(!luhn("8273 1232 7352 0569"));
}
#[allow(dead_code)]
fn main() {}
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
Copy the following code to https://play.rust-lang.org/ and make the tests pass. Try avoiding
allocating a Vec for your intermediate results:
#[test]
fn test_matches_without_wildcard() {
assert!(prefix_matches("/v1/publishers", "/v1/publishers"));
assert!(prefix_matches("/v1/publishers", "/v1/publishers/abc-123"));
assert!(prefix_matches("/v1/publishers", "/v1/publishers/abc/books"));
assert!(!prefix_matches("/v1/publishers", "/v1"));
assert!(!prefix_matches("/v1/publishers", "/v1/publishersBooks"));
assert!(!prefix_matches("/v1/publishers", "/v1/parent/publishers"));
}
#[test]
fn test_matches_with_wildcard() {
assert!(prefix_matches(
"/v1/publishers/*/books",
"/v1/publishers/foo/books"
));
assert!(prefix_matches(
"/v1/publishers/*/books",
"/v1/publishers/bar/books"
));
assert!(prefix_matches(
"/v1/publishers/*/books",
"/v1/publishers/foo/books/book1"
));
assert!(!prefix_matches("/v1/publishers/*/books", "/v1/publishers"));
assert!(!prefix_matches(
"/v1/publishers/*/books",
"/v1/publishers/foo/booksByAuthor"
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
));
traffic.
} Learn more OK, got it
Welcome to Day 3
Today, we will cover some more advanced topics of Rust:
• Traits: deriving traits, default methods, and important standard library traits.
• Generics: generic data types, generic methods, monomorphization, and trait objects.
• Unsafe Rust: raw pointers, static variables, unsafe functions, and extern functions.
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
Generics
Rust support generics, which lets you abstract algorithms or data structures (such as sorting
or a binary tree) over the types used or stored.
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
1 #[derive(Debug)]
2 struct Point<T> {
3 x: T,
4 y: T,
5 }
6
7 fn main() {
8 let integer = Point { x: 5, y: 10 };
9 let float = Point { x: 1.0, y: 4.0 };
10 println!("{integer:?} and {float:?}");
11 }
▸ Details
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
Generic Methods
You can declare a generic type on your impl block:
1 #[derive(Debug)]
2 struct Point<T>(T, T);
3
4 impl<T> Point<T> {
5 fn x(&self) -> &T {
6 &self.0 // + 10
7 }
8
9 // fn set_x(&mut self, x: T)
10 }
11
12 fn main() {
13 let p = Point(5, 10);
14 println!("p.x = {}", p.x());
15 }
▸ Details
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
Monomorphization
Generic code is turned into non-generic code based on the call sites:
1 fn main() {
2 let integer = Some(5);
3 let float = Some(5.0);
4 }
1 enum Option_i32 {
2 Some(i32),
3 None,
4 }
5
6 enum Option_f64 {
7 Some(f64),
8 None,
9 }
10
11 fn main() {
12 let integer = Option_i32::Some(5);
13 let float = Option_f64::Some(5.0);
14 }
This is a zero-cost abstraction: you get exactly the same result as if you had hand-coded the
data structures without the abstraction.
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
Traits
Rust lets you abstract over types with traits. They’re similar to interfaces:
1 trait Pet {
2 fn name(&self) -> String;
3 }
4
5 struct Dog {
6 name: String,
7 }
8
9 struct Cat;
10
11 impl Pet for Dog {
12 fn name(&self) -> String {
13 self.name.clone()
14 }
15 }
16
17 impl Pet for Cat {
18 fn name(&self) -> String {
19 String::from("The cat") // No name, cats won't respond to it anyway.
20 }
21 }
22
23 fn greet<P: Pet>(pet: &P) {
24 println!("Who's a cutie? {} is!", pet.name());
25 }
26
27 fn main() {
28 let fido = Dog { name: "Fido".into() };
29 greet(&fido);
30
31 let captain_floof = Cat;
32 greet(&captain_floof);
33 }
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
Trait Objects
Trait objects allow for values of different types, for instance in a collection:
1 trait Pet {
2 fn name(&self) -> String;
3 }
4
5 struct Dog {
6 name: String,
7 }
8
9 struct Cat;
10
11 impl Pet for Dog {
12 fn name(&self) -> String {
13 self.name.clone()
14 }
15 }
16
17 impl Pet for Cat {
18 fn name(&self) -> String {
19 String::from("The cat") // No name, cats won't respond to it anyway.
20 }
21 }
22
23 fn main() {
24 let pets: Vec<Box<dyn Pet>> = vec![
25 Box::new(Cat),
26 Box::new(Dog { name: String::from("Fido") }),
27 ];
28 for pet in pets {
29 println!("Hello {}!", pet.name());
30 }
31 }
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
Stack Heap
pets
ptr
len 2
capacity 2
name: Fido
<Dog as Pet>::name
<Cat as Pet>::name
▸ Details
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
Deriving Traits
Rust derive macros work by automatically generating code that implements the specified
traits for a data structure.
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
Default Methods
Traits can implement behavior in terms of other trait methods:
1 trait Equals {
2 fn equals(&self, other: &Self) -> bool;
3 fn not_equals(&self, other: &Self) -> bool {
4 !self.equals(other)
5 }
6 }
7
8 #[derive(Debug)]
9 struct Centimeter(i16);
10
11 impl Equals for Centimeter {
12 fn equals(&self, other: &Centimeter) -> bool {
13 self.0 == other.0
14 }
15 }
16
17 fn main() {
18 let a = Centimeter(10);
19 let b = Centimeter(20);
20 println!("{a:?} equals {b:?}: {}", a.equals(&b));
21 println!("{a:?} not_equals {b:?}: {}", a.not_equals(&b));
22 }
▸ Details
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
Trait Bounds
When working with generics, you often want to require the types to implement some trait,
so that you can call this trait’s methods.
▸ Details
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
impl Trait
Similar to trait bounds, an impl Trait syntax can be used in function arguments and return
values:
1 use std::fmt::Display;
2
3 fn get_x(name: impl Display) -> impl Display {
4 format!("Hello {name}")
5 }
6
7 fn main() {
8 let x = get_x("foo");
9 println!("{x}");
10 }
• impl Trait allows you to work with types which you cannot name.
▸ Details
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
Important Traits
We will now look at some of the most common traits of the Rust standard library:
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
Iterators
You can implement the Iterator trait on your own types:
1 struct Fibonacci {
2 curr: u32,
3 next: u32,
4 }
5
6 impl Iterator for Fibonacci {
7 type Item = u32;
8
9 fn next(&mut self) -> Option<Self::Item> {
10 let new_next = self.curr + self.next;
11 self.curr = self.next;
12 self.next = new_next;
13 Some(self.curr)
14 }
15 }
16
17 fn main() {
18 let fib = Fibonacci { curr: 0, next: 1 };
19 for (i, n) in fib.enumerate().take(5) {
20 println!("fib({i}): {n}");
21 }
22 }
▸ Details
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
FromIterator
FromIterator lets you build a collection from an Iterator .
1 fn main() {
2 let primes = vec![2, 3, 5, 7];
3 let prime_squares = primes
4 .into_iter()
5 .map(|prime| prime * prime)
6 .collect::<Vec<_>>();
7 }
▸ Details
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
1 fn main() {
2 let s = String::from("hello");
3 let addr = std::net::Ipv4Addr::from([127, 0, 0, 1]);
4 let one = i16::from(true);
5 let bigger = i32::from(123i16);
6 println!("{s}, {addr}, {one}, {bigger}");
7 }
1 fn main() {
2 let s: String = "hello".into();
3 let addr: std::net::Ipv4Addr = [127, 0, 0, 1].into();
4 let one: i16 = true.into();
5 let bigger: i32 = 123i16.into();
6 println!("{s}, {addr}, {one}, {bigger}");
7 }
▸ Details
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
1 struct Droppable {
2 name: &'static str,
3 }
4
5 impl Drop for Droppable {
6 fn drop(&mut self) {
7 println!("Dropping {}", self.name);
8 }
9 }
10
11 fn main() {
12 let a = Droppable { name: "a" };
13 {
14 let b = Droppable { name: "b" };
15 {
16 let c = Droppable { name: "c" };
17 let d = Droppable { name: "d" };
18 println!("Exiting block B");
19 }
20 println!("Exiting block A");
21 }
22 drop(a);
23 println!("Exiting main");
24 }
▸ Details
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
1 #[derive(Debug, Default)]
2 struct Derived {
3 x: u32,
4 y: String,
5 z: Implemented,
6 }
7
8 #[derive(Debug)]
9 struct Implemented(String);
10
11 impl Default for Implemented {
12 fn default() -> Self {
13 Self("John Smith".into())
14 }
15 }
16
17 fn main() {
18 let default_struct: Derived = Default::default();
19 println!("{default_struct:#?}");
20
21 let almost_default_struct = Derived {
22 y: "Y is set!".into(),
23 ..Default::default()
24 };
25 println!("{almost_default_struct:#?}");
26
27 let nothing: Option<Derived> = None;
28 println!("{:#?}", nothing.unwrap_or_default());
29 }
30
▸ Details
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
Add, Mul, …
Operator overloading is implemented via traits in std::ops :
▸ Details
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
Closures
Closures or lambda expressions have types which cannot be named. However, they
implement special Fn , FnMut , and FnOnce traits:
▸ Details
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
▸ Details
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
Copy the code below to https://play.rust-lang.org/, fill in the missing draw_into methods so
that you implement the Widget trait:
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
impl Label {
fn new(label: &str) -> Label {
Label {
label: label.to_owned(),
}
}
}
impl Button {
fn new(label: &str, callback: Box<dyn FnMut()>) -> Button {
Button {
label: Label::new(label),
callback,
}
}
}
impl Window {
This site
fnuses cookies from
new(title: Google
&str) ->to Window
deliver and
{ enhance the quality of its services and to analyze
traffic. Window
Learn more { OK, got it
title: title.to_owned(),
widgets: Vec::new(),
}
}
fn main() {
let mut window = Window::new("Rust GUI Demo 1.23");
window.add_widget(Box::new(Label::new("This
This site is of
uses cookies from Google to deliver and enhance the quality a its
small text
services andGUI demo.")));
to analyze
window.add_widget(Box::new(Button::new(
traffic. Learn more OK, got it
"Click me!",
The output of the above program can be something simple like this:
========
Rust GUI Demo 1.23
========
| Click me! |
If you want to draw aligned text, you can use the fill/alignment formatting operators. In
particular, notice how you can pad with different characters (here a '/' ) and how you can
control alignment:
1 fn main() {
2 let width = 10;
3 println!("left aligned: |{:/<width$}|", "foo");
4 println!("centered: |{:/^width$}|", "foo");
5 println!("right aligned: |{:/>width$}|", "foo");
6 }
Using such alignment tricks, you can for example produce output like this:
+--------------------------------+
| Rust GUI Demo 1.23 |
+================================+
| This is a small text GUI demo. |
| +-----------+ |
| | Click me! | |
| +-----------+ |
+--------------------------------+
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
Error Handling
Error handling in Rust is done using explicit control flow:
• Functions that can have errors list this in their return type,
• There are no exceptions.
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
Panics
Rust will trigger a panic if a fatal error happens at runtime:
1 fn main() {
2 let v = vec![10, 20, 30];
3 println!("v[100]: {}", v[100]);
4 }
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
1 use std::panic;
2
3 fn main() {
4 let result = panic::catch_unwind(|| {
5 println!("hello!");
6 });
7 assert!(result.is_ok());
8
9 let result = panic::catch_unwind(|| {
10 panic!("oh no!");
11 });
12 assert!(result.is_err());
13 }
• This can be useful in servers which should keep running even if a single request
crashes.
• This does not work if panic = 'abort' is set in your Cargo.toml .
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
1 use std::fs;
2 use std::io::Read;
3
4 fn main() {
5 let file = fs::File::open("diary.txt");
6 match file {
7 Ok(mut file) => {
8 let mut contents = String::new();
9 file.read_to_string(&mut contents);
10 println!("Dear diary: {contents}");
11 },
12 Err(err) => {
13 println!("The diary could not be opened: {err}");
14 }
15 }
16 }
▸ Details
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
match some_expression {
Ok(value) => value,
Err(err) => return Err(err),
}
some_expression?
▸ Details
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
expression?
match expression {
Ok(value) => value,
Err(err) => return Err(From::from(err)),
}
The From::from call here means we attempt to convert the error type to the type returned
by the function:
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
▸ Details
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
▸ Details
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
1 use std::fs;
2 use std::io::Read;
3 use thiserror::Error;
4 use std::error::Error;
5
6 #[derive(Clone, Debug, Eq, Error, PartialEq)]
7 #[error("Found no username in {0}")]
8 struct EmptyUsernameError(String);
9
10 fn read_username(path: &str) -> Result<String, Box<dyn Error>> {
11 let mut username = String::new();
12 fs::File::open(path)?.read_to_string(&mut username)?;
13 if username.is_empty() {
14 return Err(EmptyUsernameError(String::from(path)).into());
15 }
16 Ok(username)
17 }
18
19 fn main() {
20 //fs::write("config.dat", "").unwrap();
21 match read_username("config.dat") {
22 Ok(username) => println!("Username: {username}"),
23 Err(err) => println!("Error: {err}"),
24 }
25 }
▸ Details
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
▸ Details
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
Testing
Rust and Cargo come with a simple unit test framework:
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
Unit Tests
Mark unit tests with #[test] :
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
Test Modules
Unit tests are often put in a nested module (run tests on the Playground):
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
Documentation Tests
Rust has built-in support for documentation tests:
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
Integration Tests
If you want to test your library as a client, use an integration test.
use my_library::init;
#[test]
fn test_init() {
assert!(init().is_ok());
}
These tests only have access to the public API of your crate.
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
Here are some additional crates which we recommend for writing tests:
• googletest: Comprehensive test assertion library in the tradition of GoogleTest for C++.
• proptest: Property-based testing for Rust.
• rstest: Support for fixtures and parameterised tests.
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
Unsafe Rust
The Rust language has two parts:
We will be seeing mostly safe Rust in this course, but it’s important to know what Unsafe
Rust is.
Unsafe code is usually small and isolated, and its correctness should be carefully
documented. It is usually wrapped in a safe abstraction layer.
We will briefly cover unsafe capabilities next. For full details, please see Chapter 19.1 in the
Rust Book and the Rustonomicon.
▸ Details
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
1 fn main() {
2 let mut num = 5;
3
4 let r1 = &mut num as *mut i32;
5 let r2 = r1 as *const i32;
6
7 // Safe because r1 and r2 were obtained from references and so are
8 // guaranteed to be non-null and properly aligned, the objects underlying
9 // the references from which they were obtained are live throughout the
10 // whole unsafe block, and they are not accessed either through the
11 // references or concurrently through any other pointers.
12 unsafe {
13 println!("r1 is: {}", *r1);
14 *r1 = 10;
15 println!("r2 is: {}", *r2);
16 }
17 }
▸ Details
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
However, since data races can occur, it is unsafe to read and write mutable static variables:
▸ Details
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
Unions
Unions are like enums, but you need to track the active field yourself:
1 #[repr(C)]
2 union MyUnion {
3 i: u8,
4 b: bool,
5 }
6
7 fn main() {
8 let u = MyUnion { i: 42 };
9 println!("int: {}", unsafe { u.i });
10 println!("bool: {}", unsafe { u.b }); // Undefined behavior!
11 }
▸ Details
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
1 fn main() {
2 �∈�
let emojis = "� �";
3
4 // Safe because the indices are in the correct order, within the bounds of
5 // the string slice, and lie on UTF-8 sequence boundaries.
6 unsafe {
7 println!("emoji: {}", emojis.get_unchecked(0..4));
8 println!("emoji: {}", emojis.get_unchecked(4..7));
9 println!("emoji: {}", emojis.get_unchecked(7..11));
10 }
11
12 println!("char count: {}", count_chars(unsafe { emojis.get_unchecked(0..
13
14 // Not upholding the UTF-8 encoding requirement breaks memory safety!
15 // println!("emoji: {}", unsafe { emojis.get_unchecked(0..3) });
16 // println!("char count: {}", count_chars(unsafe { emojis.get_unchecked(0..3) })
17 }
18
19 fn count_chars(s: &str) -> usize {
20 s.chars().map(|_| 1).sum()
21 }
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
▸ Details
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
1 extern "C" {
2 fn abs(input: i32) -> i32;
3 }
4
5 fn main() {
6 unsafe {
7 // Undefined behavior if abs misbehaves.
8 println!("Absolute value of -3 according to C: {}", abs(-3));
9 }
10 }
▸ Details
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
For example, the zerocopy crate has an unsafe trait that looks something like this:
1 use std::mem::size_of_val;
2 use std::slice;
3
4 /// ...
5 /// # Safety
6 /// The type must have a defined representation and no padding.
7 pub unsafe trait AsBytes {
8 fn as_bytes(&self) -> &[u8] {
9 unsafe {
10 slice::from_raw_parts(self as *const Self as *const u8, size_of_val
11 }
12 }
13 }
14
15 // Safe because u32 has a defined representation and no padding.
16 unsafe impl AsBytes for u32 {}
▸ Details
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
For this exercise, we suggest using a local dev environment instead of the Playground. This
will allow you to run your binary on your own machine.
▸ Details
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
• opendir(3)
• readdir(3)
• closedir(3)
You will also want to browse the std::ffi module. There you find a number of string types
which you need for the exercise:
Copy the code below to https://play.rust-lang.org/ and fill in the missing functions and
methods:
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
mod ffi {
use std::os::raw::{c_char, c_int};
#[cfg(not(target_os = "macos"))]
use std::os::raw::{c_long, c_ulong, c_ushort, c_uchar};
// Layout according to the Linux man page for readdir(3), where ino_t and
// off_t are resolved according to the definitions in
// /usr/include/x86_64-linux-gnu/{sys/types.h, bits/typesizes.h}.
#[cfg(not(target_os = "macos"))]
#[repr(C)]
pub struct dirent {
pub d_ino: c_ulong,
pub d_off: c_long,
pub d_reclen: c_ushort,
pub d_type: c_uchar,
pub d_name: [c_char; 256],
}
extern "C" {
pub fn opendir(s: *const c_char) -> *mut DIR;
#[derive(Debug)]
struct DirectoryIterator {
path: CString,
dir: *mut ffi::DIR,
}
impl DirectoryIterator {
fn new(path: &str) -> Result<DirectoryIterator, String> {
// Call opendir and return a Ok value if that worked,
// otherwise return Err with a message.
unimplemented!()
}
}
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
We will attempt to call Rust from one of your own projects today. So try to find a little
corner of your code base where we can move some lines of code to Rust. The fewer
dependencies and “exotic” types the better. Something that parses some raw bytes
would be ideal.
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
Setup
We will be using an Android Virtual Device to test our code. Make sure you have access to
one or create a new one with:
source build/envsetup.sh
lunch aosp_cf_x86_64_phone-userdebug
acloud create
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
Build Rules
The Android build system (Soong) supports Rust via a number of modules:
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
Rust Binaries
Let us start with a simple application. At the root of an AOSP checkout, create the following
files:
hello_rust/Android.bp:
rust_binary {
name: "hello_rust",
crate_name: "hello_rust",
srcs: ["src/main.rs"],
}
hello_rust/src/main.rs:
m hello_rust
adb push "$ANDROID_PRODUCT_OUT/system/bin/hello_rust /data/local/tmp"
adb shell /data/local/tmp/hello_rust
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
Rust Libraries
You use rust_library to create a new Rust library for Android.
hello_rust/Android.bp:
rust_binary {
name: "hello_rust_with_dep",
crate_name: "hello_rust_with_dep",
srcs: ["src/main.rs"],
rustlibs: [
"libgreetings",
"libtextwrap",
],
prefer_rlib: true,
}
rust_library {
name: "libgreetings",
crate_name: "greetings",
srcs: ["src/lib.rs"],
}
hello_rust/src/main.rs:
use greetings::greeting;
use textwrap::fill;
hello_rust/src/lib.rs:
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
m hello_rust_with_dep
adb push "$ANDROID_PRODUCT_OUT/system/bin/hello_rust_with_dep /data/local/tmp"
adb shell /data/local/tmp/hello_rust_with_dep
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
AIDL
The Android Interface Definition Language (AIDL) is supported in Rust:
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
AIDL Interfaces
You declare the API of your service using an AIDL interface:
birthday_service/aidl/com/example/birthdayservice/IBirthdayService.aidl:
package com.example.birthdayservice;
birthday_service/aidl/Android.bp:
aidl_interface {
name: "com.example.birthdayservice",
srcs: ["com/example/birthdayservice/*.aidl"],
unstable: true,
backend: {
rust: { // Rust is not enabled by default
enabled: true,
},
},
}
Add vendor_available: true if your AIDL file is used by a binary in the vendor partition.
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
Service Implementation
We can now implement the AIDL service:
birthday_service/src/lib.rs:
birthday_service/Android.bp:
rust_library {
name: "libbirthdayservice",
srcs: ["src/lib.rs"],
crate_name: "birthdayservice",
rustlibs: [
"com.example.birthdayservice-rust",
"libbinder_rs",
],
}
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
AIDL Server
Finally, we can create a server which exposes the service:
birthday_service/src/server.rs:
birthday_service/Android.bp:
rust_binary {
name: "birthday_server",
crate_name: "birthday_server",
srcs: ["src/server.rs"],
rustlibs: [
"com.example.birthdayservice-rust",
"libbinder_rs",
"libbirthdayservice",
],
prefer_rlib: true,
}
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
Deploy
We can now build, push, and start the service:
m birthday_server
adb push "$ANDROID_PRODUCT_OUT/system/bin/birthday_server /data/local/tmp"
adb shell /data/local/tmp/birthday_server
Result: Parcel(
0x00000000: 00000000 00000036 00610048 00700070 '....6...H.a.p.p.'
0x00000010: 00200079 00690042 00740072 00640068 'y. .B.i.r.t.h.d.'
0x00000020: 00790061 00420020 0062006f 0020002c 'a.y. .B.o.b.,. .'
0x00000030: 006f0063 0067006e 00610072 00750074 'c.o.n.g.r.a.t.u.'
0x00000040: 0061006c 00690074 006e006f 00200073 'l.a.t.i.o.n.s. .'
0x00000050: 00690077 00680074 00740020 00650068 'w.i.t.h. .t.h.e.'
0x00000060: 00320020 00200034 00650079 00720061 ' .2.4. .y.e.a.r.'
0x00000070: 00210073 00000000 's.!..... ')
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
AIDL Client
Finally, we can create a Rust client for our new service.
birthday_service/src/client.rs:
binder::ProcessState::start_thread_pool();
let service = connect().expect("Failed to connect to BirthdayService");
let msg = service.wishHappyBirthday(&name, years)?;
println!("{msg}");
Ok(())
}
birthday_service/Android.bp:
rust_binary {
name: "birthday_client",
crate_name: "birthday_client",
srcs: ["src/client.rs"],
rustlibs: [
"com.example.birthdayservice-rust",
"libbinder_rs",
],
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
prefer_rlib: true,
}
traffic. Learn more OK, got it
m birthday_client
adb push "$ANDROID_PRODUCT_OUT/system/bin/birthday_client /data/local/tmp"
adb shell /data/local/tmp/birthday_client Charlie 60
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
Changing API
Let us extend the API with more functionality: we want to let clients specify a list of lines for
the birthday card:
package com.example.birthdayservice;
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
Logging
You should use the log crate to automatically log to logcat (on-device) or stdout (on-
host):
hello_rust_logs/Android.bp:
rust_binary {
name: "hello_rust_logs",
crate_name: "hello_rust_logs",
srcs: ["src/main.rs"],
rustlibs: [
"liblog_rust",
"liblogger",
],
prefer_rlib: true,
host_supported: true,
}
hello_rust_logs/src/main.rs:
m hello_rust_logs
adb push "$ANDROID_PRODUCT_OUT/system/bin/hello_rust_logs /data/local/tmp"
adb shell /data/local/tmp/hello_rust_logs
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
Interoperability
Rust has excellent support for interoperability with other languages. This means that you
can:
When you call functions in a foreign language we say that you’re using a foreign function
interface, also known as FFI.
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
Interoperability with C
Rust has full support for linking object files with a C calling convention. Similarly, you can
export Rust functions and call them from C.
extern "C" {
fn abs(x: i32) -> i32;
}
fn main() {
let x = -42;
let abs_x = unsafe { abs(x) };
println!("{x}, {abs_x}");
}
This assumes full knowledge of the target platform. Not recommended for production.
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
Using Bindgen
The bindgen tool can auto-generate bindings from a C header file.
interoperability/bindgen/libbirthday.h:
interoperability/bindgen/libbirthday.c:
#include <stdio.h>
#include "libbirthday.h"
interoperability/bindgen/Android.bp:
cc_library {
name: "libbirthday",
srcs: ["libbirthday.c"],
}
Create a wrapper header file for the library (not strictly needed in this example):
interoperability/bindgen/libbirthday_wrapper.h:
#include "libbirthday.h"
rust_bindgen {
name: "libbirthday_bindgen",
crate_name: "birthday_bindgen",
wrapper_src: "libbirthday_wrapper.h",
source_stem: "bindings",
static_libs: ["libbirthday"],
}
interoperability/bindgen/Android.bp:
rust_binary {
name: "print_birthday_card",
srcs: ["main.rs"],
rustlibs: ["libbirthday_bindgen"],
}
interoperability/bindgen/main.rs:
fn main() {
let name = std::ffi::CString::new("Peter").unwrap();
let card = card {
name: name.as_ptr(),
years: 42,
};
unsafe {
print_card(&card as *const card);
}
}
m print_birthday_card
adb push "$ANDROID_PRODUCT_OUT/system/bin/print_birthday_card /data/local/tmp"
adb shell /data/local/tmp/print_birthday_card
interoperability/bindgen/Android.bp:
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
rust_test {
name: "libbirthday_bindgen_test",
srcs: [":libbirthday_bindgen"],
crate_name: "libbirthday_bindgen_test",
test_suites: ["general-tests"],
auto_gen_config: true,
clippy_lints: "none", // Generated file, skip linting
lints: "none",
}
atest libbirthday_bindgen_test
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
Calling Rust
Exporting Rust functions and types to C is easy:
interoperability/rust/libanalyze/analyze.rs
interoperability/rust/libanalyze/analyze.h
#ifndef ANALYSE_H
#define ANALYSE_H
extern "C" {
void analyze_numbers(int x, int y);
}
#endif
interoperability/rust/libanalyze/Android.bp
rust_ffi {
name: "libanalyze_ffi",
crate_name: "analyze_ffi",
srcs: ["analyze.rs"],
include_dirs: ["."],
}
interoperability/rust/analyze/main.c
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
#include "analyze.h"
int main() {
analyze_numbers(10, 20);
analyze_numbers(123, 123);
return 0;
}
interoperability/rust/analyze/Android.bp
cc_binary {
name: "analyze_numbers",
srcs: ["main.c"],
static_libs: ["libanalyze_ffi"],
}
m analyze_numbers
adb push "$ANDROID_PRODUCT_OUT/system/bin/analyze_numbers /data/local/tmp"
adb shell /data/local/tmp/analyze_numbers
▸ Details
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
With C++
The CXX crate makes it possible to do safe interoperability between Rust and C++.
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
interoperability/java/src/lib.rs:
interoperability/java/Android.bp:
rust_ffi_shared {
name: "libhello_jni",
crate_name: "hello_jni",
srcs: ["src/lib.rs"],
rustlibs: ["libjni"],
}
interoperability/java/HelloWorld.java:
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
class HelloWorld {
private static native String hello(String name);
static {
System.loadLibrary("hello_jni");
}
interoperability/java/Android.bp:
java_binary {
name: "helloworld_jni",
srcs: ["HelloWorld.java"],
main_class: "HelloWorld",
required: ["libhello_jni"],
}
m helloworld_jni
adb sync # requires adb root && adb remount
adb shell /system/bin/helloworld_jni
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
Exercises
This is a group exercise: We will look at one of the projects you work with and try to
integrate some Rust into it. Some suggestions:
▸ Details
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
Today we will talk about ‘bare-metal’ Rust: running Rust code without an OS underneath us.
This will be divided into several parts:
For the microcontroller part of the course we will use the BBC micro:bit v2 as an example.
It’s a development board based on the Nordic nRF51822 microcontroller with some LEDs
and buttons, an I2C-connected accelerometer and compass, and an on-board SWD
debugger.
To get started, install some tools we’ll need later. On gLinux or Debian:
And give users in the plugdev group access to the micro:bit programmer:
On MacOS:
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
xcode-select --install
brew install gdb picocom qemu
brew install --cask gcc-aarch64-embedded
rustup update
rustup target add aarch64-unknown-none thumbv7em-none-eabihf
rustup component add llvm-tools-preview
cargo install cargo-binutils cargo-embed
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
no_std
▸ Details
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
▸ Details
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
alloc
To use alloc you must implement a global (heap) allocator.
1 #![no_main]
2 #![no_std]
3
4 extern crate alloc;
5 extern crate panic_halt as _;
6
7 use alloc::string::ToString;
8 use alloc::vec::Vec;
9 use buddy_system_allocator::LockedHeap;
10
11 #[global_allocator]
12 static HEAP_ALLOCATOR: LockedHeap<32> = LockedHeap::<32>::new();
13
14 static mut HEAP: [u8; 65536] = [0; 65536];
15
16 pub fn entry() {
17 // Safe because `HEAP` is only used here and `entry` is only called once.
18 unsafe {
19 // Give the allocator some memory to allocate.
20 HEAP_ALLOCATOR
21 .lock()
22 .init(HEAP.as_mut_ptr() as usize, HEAP.len());
23 }
24
25 // Now we can do things that require heap allocation.
26 let mut v = Vec::new();
27 v.push("A string".to_string());
28 }
▸ Details
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
Microcontrollers
The cortex_m_rt crate provides (among other things) a reset handler for Cortex M
microcontrollers.
1 #![no_main]
2 #![no_std]
3
4 extern crate panic_halt as _;
5
6 mod interrupts;
7
8 use cortex_m_rt::entry;
9
10 #[entry]
11 fn main() -> ! {
12 loop {}
13 }
Next we’ll look at how to access peripherals, with increasing levels of abstraction.
▸ Details
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
Raw MMIO
Most microcontrollers access peripherals via memory-mapped IO. Let’s try turning on an
LED on our micro:bit:
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
1 #![no_main]
2 #![no_std]
3
4 extern crate panic_halt as _;
5
6 mod interrupts;
7
8 use core::mem::size_of;
9 use cortex_m_rt::entry;
10
11 /// GPIO port 0 peripheral address
12 const GPIO_P0: usize = 0x5000_0000;
13
14 // GPIO peripheral offsets
15 const PIN_CNF: usize = 0x700;
16 const OUTSET: usize = 0x508;
17 const OUTCLR: usize = 0x50c;
18
19 // PIN_CNF fields
20 const DIR_OUTPUT: u32 = 0x1;
21 const INPUT_DISCONNECT: u32 = 0x1 << 1;
22 const PULL_DISABLED: u32 = 0x0 << 2;
23 const DRIVE_S0S1: u32 = 0x0 << 8;
24 const SENSE_DISABLED: u32 = 0x0 << 16;
25
26 #[entry]
27 fn main() -> ! {
28 // Configure GPIO 0 pins 21 and 28 as push-pull outputs.
29 let pin_cnf_21 = (GPIO_P0 + PIN_CNF + 21 * size_of::<u32>()) as *mut u32
30 let pin_cnf_28 = (GPIO_P0 + PIN_CNF + 28 * size_of::<u32>()) as *mut u32
31 // Safe because the pointers are to valid peripheral control registers, and
32 // no aliases exist.
33 unsafe {
34 pin_cnf_21.write_volatile(
35 DIR_OUTPUT | INPUT_DISCONNECT | PULL_DISABLED | DRIVE_S0S1 | SENSE_DISAB
36 );
37 pin_cnf_28.write_volatile(
38 DIR_OUTPUT | INPUT_DISCONNECT | PULL_DISABLED | DRIVE_S0S1 | SENSE_DISAB
39 );
40 }
41
42 // Set pin 28 low and pin 21 high to turn the LED on.
43 let gpio0_outset = (GPIO_P0 + OUTSET) as *mut u32;
44 let gpio0_outclr = (GPIO_P0 + OUTCLR) as *mut u32;
45 // Safe because the pointers are to valid peripheral control registers, and
46 // no aliases exist.
47 unsafe {
48 gpio0_outclr.write_volatile(1 << 28);
49 gpio0_outset.write_volatile(1 << 21);
50 }
This51site uses cookies from Google to deliver and enhance the quality of its services and to analyze
52 loop {}
traffic. Learn more OK, got it
53 }
▸ Details
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
1 #![no_main]
2 #![no_std]
3
4 extern crate panic_halt as _;
5
6 use cortex_m_rt::entry;
7 use nrf52833_pac::Peripherals;
8
9 #[entry]
10 fn main() -> ! {
11 let p = Peripherals::take().unwrap();
12 let gpio0 = p.P0;
13
14 // Configure GPIO 0 pins 21 and 28 as push-pull outputs.
15 gpio0.pin_cnf[21].write(|w| {
16 w.dir().output();
17 w.input().disconnect();
18 w.pull().disabled();
19 w.drive().s0s1();
20 w.sense().disabled();
21 w
22 });
23 gpio0.pin_cnf[28].write(|w| {
24 w.dir().output();
25 w.input().disconnect();
26 w.pull().disabled();
27 w.drive().s0s1();
28 w.sense().disabled();
29 w
30 });
31
32 // Set pin 28 low and pin 21 high to turn the LED on.
33 gpio0.outclr.write(|w| w.pin28().clear());
34 gpio0.outset.write(|w| w.pin21().set());
35
36 loop {}
37 }
▸ Details
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
HAL crates
HAL crates for many microcontrollers provide wrappers around various peripherals. These
generally implement traits from embedded-hal .
1 #![no_main]
2 #![no_std]
3
4 extern crate panic_halt as _;
5
6 use cortex_m_rt::entry;
7 use nrf52833_hal::gpio::{p0, Level};
8 use nrf52833_hal::pac::Peripherals;
9 use nrf52833_hal::prelude::*;
10
11 #[entry]
12 fn main() -> ! {
13 let p = Peripherals::take().unwrap();
14
15 // Create HAL wrapper for GPIO port 0.
16 let gpio0 = p0::Parts::new(p.P0);
17
18 // Configure GPIO 0 pins 21 and 28 as push-pull outputs.
19 let mut col1 = gpio0.p0_28.into_push_pull_output(Level::High);
20 let mut row1 = gpio0.p0_21.into_push_pull_output(Level::Low);
21
22 // Set pin 28 low and pin 21 high to turn the LED on.
23 col1.set_low().unwrap();
24 row1.set_high().unwrap();
25
26 loop {}
27 }
▸ Details
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
1 #![no_main]
2 #![no_std]
3
4 extern crate panic_halt as _;
5
6 use cortex_m_rt::entry;
7 use microbit::hal::prelude::*;
8 use microbit::Board;
9
10 #[entry]
11 fn main() -> ! {
12 let mut board = Board::take().unwrap();
13
14 board.display_pins.col1.set_low().unwrap();
15 board.display_pins.row1.set_high().unwrap();
16
17 loop {}
18 }
▸ Details
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
▸ Details
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
embedded-hal
The embedded-hal crate provides a number of traits covering common microcontroller
peripherals.
• GPIO
• ADC
• I2C, SPI, UART, CAN
• RNG
• Timers
• Watchdogs
Other crates then implement drivers in terms of these traits, e.g. an accelerometer driver
might need an I2C or SPI bus implementation.
▸ Details
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
probe-rs, cargo-embed
probe-rs is a handy toolset for embedded debugging, like OpenOCD but better integrated.
cargo-embed is a cargo subcommand to build and flash binaries, log RTT output and
connect GDB. It’s configured by an Embed.toml file in your project directory.
▸ Details
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
Debugging
Embed.toml:
[default.general]
chip = "nrf52833_xxAA"
[debug.gdb]
enabled = true
▸ Details
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
Other projects
• RTIC
◦ “Real-Time Interrupt-driven Concurrency”
◦ Shared resource management, message passing, task scheduling, timer queue
• Embassy
◦ async executors with priorities, timers, networking, USB
• TockOS
◦ Security-focused RTOS with preemptive scheduling and Memory Protection Unit
support
• Hubris
◦ Microkernel RTOS from Oxide Computer Company with memory protection,
unprivileged drivers, IPC
• Bindings for FreeRTOS
• Some platforms have std implementations, e.g. esp-idf.
▸ Details
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
Exercises
We will read the direction from an I2C compass, and log the readings to a serial port.
▸ Details
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
Compass
We will read the direction from an I2C compass, and log the readings to a serial port. If you
have time, try displaying it on the LEDs somehow too, or use the buttons somehow.
Hints:
• Check the documentation for the lsm303agr and microbit-v2 crates, as well as the
micro:bit hardware.
• The LSM303AGR Inertial Measurement Unit is connected to the internal I2C bus.
• TWI is another name for I2C, so the I2C master peripheral is called TWIM.
• The LSM303AGR driver needs something implementing the
embedded_hal::blocking::i2c::WriteRead trait. The microbit::hal::Twim struct
implements this.
• You have a microbit::Board struct with fields for the various pins and peripherals.
• You can also look at the nRF52833 datasheet if you want, but it shouldn’t be necessary
for this exercise.
Download the exercise template and look in the compass directory for the following files.
src/main.rs :
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
#![no_main]
#![no_std]
use core::fmt::Write;
use cortex_m_rt::entry;
use microbit::{hal::uarte::{Baudrate, Parity, Uarte}, Board};
#[entry]
fn main() -> ! {
let board = Board::take().unwrap();
writeln!(serial, "Ready.").unwrap();
loop {
// Read compass data and log it to the serial port.
// TODO
}
}
[workspace]
[package]
name = "compass"
version = "0.1.0"
edition = "2021"
publish = false
[dependencies]
cortex-m-rt = "0.7.3"
embedded-hal = "0.2.6"
lsm303agr = "0.2.2"
microbit-v2 = "0.13.0"
panic-halt = "0.2.0"
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
Learn (you
Embed.toml
traffic. more shouldn’t
OK, got itneed to change this):
[default.general]
chip = "nrf52833_xxAA"
[debug.gdb]
enabled = true
[debug.reset]
halt_afterwards = true
[build]
target = "thumbv7em-none-eabihf" # Cortex-M4F
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
Application processors
So far we’ve talked about microcontrollers, such as the Arm Cortex-M series. Now let’s try
writing something for Cortex-A. For simplicity we’ll just work with QEMU’s aarch64 ‘virt’
board.
▸ Details
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
/*
* Ensure everything before this point has completed, then invalidate any
* potentially stale local TLB entries before they start being used.
*/
isb
tlbi vmalle1
ic iallu
dsb nsh
isb
/*
* Configure sctlr_el1 to enable MMU and cache and don't proceed until this
* has completed.
*/
msr sctlr_el1, x30
isb
b 0b
▸ Details
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
Inline assembly
Sometimes we need to use assembly to do things that aren’t possible with Rust code. For
example, to make an HVC to tell the firmware to power off the system:
1 #![no_main]
2 #![no_std]
3
4 use core::arch::asm;
5 use core::panic::PanicInfo;
6
7 mod exceptions;
8
9 const PSCI_SYSTEM_OFF: u32 = 0x84000008;
10
11 #[no_mangle]
12 extern "C" fn main(_x0: u64, _x1: u64, _x2: u64, _x3: u64) {
13 // Safe because this only uses the declared registers and doesn't do
14 // anything with memory.
15 unsafe {
16 asm!("hvc #0",
17 inout("w0") PSCI_SYSTEM_OFF => _,
18 inout("w1") 0 => _,
19 inout("w2") 0 => _,
20 inout("w3") 0 => _,
21 inout("w4") 0 => _,
22 inout("w5") 0 => _,
23 inout("w6") 0 => _,
24 inout("w7") 0 => _,
25 options(nomem, nostack)
26 );
27 }
28
29 loop {}
30 }
(If you actually want to do this, use the smccc crate which has wrappers for all these
functions.)
▸ Details
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it
▸ Details
This site uses cookies from Google to deliver and enhance the quality of its services and to analyze
traffic. Learn more OK, got it