The Rust Programming Language: an Overview

RobertoCasadei 1,160 views 61 slides May 26, 2019
Slide 1
Slide 1 of 61
Slide 1
1
Slide 2
2
Slide 3
3
Slide 4
4
Slide 5
5
Slide 6
6
Slide 7
7
Slide 8
8
Slide 9
9
Slide 10
10
Slide 11
11
Slide 12
12
Slide 13
13
Slide 14
14
Slide 15
15
Slide 16
16
Slide 17
17
Slide 18
18
Slide 19
19
Slide 20
20
Slide 21
21
Slide 22
22
Slide 23
23
Slide 24
24
Slide 25
25
Slide 26
26
Slide 27
27
Slide 28
28
Slide 29
29
Slide 30
30
Slide 31
31
Slide 32
32
Slide 33
33
Slide 34
34
Slide 35
35
Slide 36
36
Slide 37
37
Slide 38
38
Slide 39
39
Slide 40
40
Slide 41
41
Slide 42
42
Slide 43
43
Slide 44
44
Slide 45
45
Slide 46
46
Slide 47
47
Slide 48
48
Slide 49
49
Slide 50
50
Slide 51
51
Slide 52
52
Slide 53
53
Slide 54
54
Slide 55
55
Slide 56
56
Slide 57
57
Slide 58
58
Slide 59
59
Slide 60
60
Slide 61
61

About This Presentation

Brief overview of the Rust system programming language. Provides a concise introduction of its basic features, with an emphasis on its memory safety features (ownership, moves, borrowing) and programming style with generic functions, structures, and traits.


Slide Content

Rust
An Overview
Roberto Casadei
PSLab
Department of Computer Science and Engineering (DISI)
Alma Mater Studiorum – Università of Bologna
https://github.com/metaphori/learning-rust
May 26, 2019
R. Casadei Intro 1/61

Outline
1
Intro
2
Ownership, borrows, moves
3
UDTs
4
More
R. Casadei Intro 2/61

Rust: a one-slide overview
Rust is a PL forsystem programmingdeveloped by Mozilla&co
System programming isresource-constrained programming—cf., OS, device drivers,
FSs, DBs, Media, Memory, Networking, Virtualisation, Scientic simulations, Games..
Rust is aahead-of-time compiledPL
Rust is atype-safePL (checks ensure well-denedness, i.e., no undened behaviour)
Goal:
Icompile-time checks of ownership, moves, borrows
Goal:
Icompile-time checks preventing data races, misuse of sync primitives
Who uses Rust?e.g., Dropbox (seehttps://www.rust-lang.org/production )
What developers say?Rust is the most loved PL as StackOverow surveys 2016-19
Key technical aspects:
Rust tracks theownershipandlifetimesof values, so mistakes like dangling pointers,
double frees, and pointer invalidation are ruled out at compile time.
R. Casadei Intro 3/61

Toolchain
Install:(https://rustup.rs)
rustup: the Rust installer / toolchain manager
rustc: the Rust compiler
Source:main.rs
fn
println!("Hello", std::env::args());
}Compiling and running
$ rustc main.rs
$ ./main # Hello world
cargo: Rust's compilation manager, package manager, and general-purpose tool
cargo new (--bin|--lib) hello creates a new Rust package underhello/
IFileCargo.tomlholds metadata for the package
cargo buildandcargo run
ICargo.lockkeeps track of theexact versions of deps. Output intarget/debug
rustdoc: Rust documentation tool
On REPL:an open issue (rusticrate exists but has some problems)
R. Casadei Intro 4/61

Project layout(s)
Cargo.lock
Cargo.toml
benches/
large-input.rs
examples/
simple.rs
src/
bin/
another_executable.rs
lib.rs
main.rs
tests/
some-integration-tests.rs
Cargo.tomlandCargo.lockare stored in the root of your package (package root).
Source code goes insrc/
The default library le issrc/lib.rs
The default executable le issrc/main.rs
Other executables can be placed insrc/bin/*.rs.
Integration tests go intests/
oUnit tests go in each le they're testing
Examples go inexamples/
Benchmarks go inbenches/
R. Casadei Intro 5/61

Cargo.toml example
cargo.tomlexample
cargo-features = ["default-run"]
[package]
name =myproj"
version =0.1.0"
authors = ["[email protected]>"]
default-run =myBin1"
edition =2018"extern"
[[bin]]
name =myBin1"
path =src/main1.rs"
[[bin]]
name =myBin2"
path =src/main2.rs"
[dependencies]
myDep1 =0.3.2".io
myDep1b =>=1.0.5"
myDep2 = { path =../path/to/my/dep2"
myDep3 = { git =https://github.com/.../dep3.git", rev =528f19c"
[profile.debug]cargo
[profile.relase]cargorelease
debug = true
[profile.test]cargo
Cargo understandsSemVer(MAJOR.MINOR.PATH):0.3.14is actually shorthand for
^0.3.14, which means “any version that has a public API compatible with version0.3.14”.
I.e., by default, Cargo will only look for versions larger than0.3.14and smaller than
0.4.0.
R. Casadei Intro 6/61

Projects, Packages, Crates, Modules and Items
Package: a Cargo project from which one or morecratesare built, tested, shared
Crate: a Rust package (library or executable)—for sharing codebetweenprojects
Modules(keywordmod) are namespaces that help organising codewithina project
Standard librarystdis automatically linked (but notused) with every project.
Operator::to access module features;selfandsuper
Module in les: when Rust seesmod , it checks for bothxxx.rsandxxx/mod.rs
On visibility:private by default (siblings + children);pub(through parent)
A module is a collection of named features akaitems
Functionsfn
Types: user-def types introduced viastruct,enum,trait.Type aliases:type
Impl blocks:impl ,impl
Constants:pub
Module: a module can contain sub-modules (public or private like any other named item)
Imports:useandextern decls; these are aliases which can be made public
oextern is not needed withedition="2018" in Cargo.toml
Extern blocks: declare a set of functions, written in some PL, callable from Rust code
Any item can be decorated withattributes:#(test)
R. Casadei Intro 7/61

Basics
Comments
//
/*Comment*/
///let declarations, (immutable) variables, mutables, constants
fn
let);;
let
//
let<i32> =::new();
println!("{}", v.len());
//.push(1);:
let<u64> =::new();Blocks: any block surrounded by curly braces can function as an expression
let:,
let
20; ; 10 };;;Scalar types
letf32::INFINITY;
let;
let"hello") };
let)+(2);:t
R. Casadei Intro 8/61

Basics
Match expressions
let<i32,&str> =(10);
let(success) => *..*/,(error) => *..*/Basic I/O
println!("{:.4}b}", std::f64::consts::PI,,foo", 7,(10));
let::new();
if(l) = std::io::stdin().read_line(&mut
leti32>().expect(&format!("Invalid", s));
}Type aliases/constructors
//::io::Result,Result,
//::io::Error.
type<T> = std::result::Result<T,>Control structures (are expressions)
let
let *...*/ *...*/ *...*/
let *x;
while *x; };
bar:
for *...*/
for"{};",e) };
R. Casadei Intro 9/61

Basics
Compound types
////
let);
leti32,bool) = (77,);
println!("{}", tp.0);
////T;N]:
leti32;5] = [1, 2, 3, 4, 5];
a1[500];
let
////<T>:,ed
let<i32> =::new();
v1.push(10);
let
////
letstaticfoo";
letfoo".to_string();
let::from("bar");
////T]:
leti32] = &a1[1..];
letstr
////:
leti32,&str) = (12,eggs");
let::new(v);
println!("{}", b.1);
AVec<T> has 3 values:(1) pointer to content on heap; (2) capacity; (3) actual len
Areference to a slice&[T]is a: a 2-word val comprising (1) ptr to the
slice's rst elem, and (2) the num of elems in the slice
Box::new(v) allocates some heap space, movesvinto it, and returns a Box ptr
R. Casadei Intro 10/61

Basics
UDTs
struct, y:
structi32,char)
struct
enumu32) }sum)
let
let
let
let
impl
fnself) ->
E::B(i) => E::B(i+1), _ => E::A
} } }
pub<RHS=Self> {already)
type;
fnself, rhs: RHS) ->::Output;
}
impl
type
fnself, rhs: E) ->::Output
matchself,rhs) { (B(i),B(j)) => B(i+j), _ => E::A }
} }
R. Casadei Intro 11/61

Basics
Functions and closures
fn) ->
fn>(s: &str, sep:) -><(T, T)> {
let
letmust)Error handling
let<i32,&str> = *...*/;
let(success) => *..*/,(error) => *..*/
type<i32,String>;
fn(r1?+r2?); }
letOk(60),Ok(30));
println!("RI:", ri.unwrap());
let<i32> = ri.ok();:
println!("Panic:", std::panic::catch_unwind(|| {
let
}).ok());:Unit testing: Rust/Cargo provide basic support for testing
#[cfg(test)] mod
#[test] fn
R. Casadei Intro 12/61

Example: program
usestr::FromStr;
useWrite;
fnt>
let<u64> =::new();
for
ifhelp"
writeln!(std::io::stderr(),Usage:").unwrap();
std::process::exit(1);
}
numbers.push(u64::from_str(&arg)
.expect("error"));
}
println!("{}", numbers[0]);
for"{}", *m); }
}
Vecis Rust's growable vector type
std::env::args()returns an
unwrap()call is a terse way to check the attempt to print the error did not itself fail.
from_strreturns aResultvalue which is eitherOk(v)orErr(e); methodexpectextracts
the value if ok or panics otherwise.
In the iteration, numbers, and we
the vector's elements inmvia operator&. Operator*is for dereferencing.
Rust assumes that if main returns at all, the program nished successfully.
R. Casadei Intro 13/61

Example: guess a number
Cargo.toml
[dependencies]
rand = "0.3.14"
extern
useOrdering;
fn
let
loop{
let::new();
println!("Guess:);
io::stdin().read_line(&mut"Failed");
let
Ok(num) => num,(_) =>,
};
match
Ordering::Less => println!("Too"),
Ordering::Greater => println!("Too"),
Ordering::Equal => { println!("You");
}
}
}
Without theuse, we had to writestd::io::stdin()
rand::thread_rng()gives a random gen local to current thread and seeded by OS
stdinreturns aStdininstance;read_line()puts text from stdin to the variable passed
by reference and returns an object ofenum typeio::Result admitting valuesOkorErr.
On aErrvalue,expect(m)will crash the program displayingm.
R. Casadei Intro 14/61

Example: webserver
[dependencies]
iron =0.5.1"
mime =0.2.3"
router =0.5.1"
urlencoded =0.5.0"
extern #[macro_use] extern
use *;
fn
let
router.get("/", get_logic,root");
router.get("/hello", get_logic,pageId1");
println!("Serving://localhost:3000...");
Iron::new(router).http("localhost:3000").unwrap();
}
fnmut
let
response.set_mut(status::Ok);
response.set_mut(mime!(Text/Html; Charset=Utf8));
response.set_mut(r#"<h1>Hello</h1>"#);
Ok(response)
}
R. Casadei Intro 15/61

Outline
1
Intro
2
Ownership, borrows, moves
3
UDTs
4
More
R. Casadei Intro 16/61

On memory management
Stack: LIFO; fast to write; fast to read since it must take up a known, xed size.
Heap: to store data withunknown size at compile time or dynamic size; youallocate
some space and get apointerto the address of that location.
Pattern: xed-size handle on stack pointing to a variable amount of data on the heap
struct, birth:
let::new();
composers.push(Person {name: Palestrina.to_string(), birth: 1525 });
Varcomposersown its vector; the vector owns its elems; each elem is aPersonstructure
which holds its elds; and the string eld owns its text.
R. Casadei Intro 17/61

Rust memory management and safety
Rust makes the following pair of promises fundamental to system-programming
1)You decide the lifetime of each value of your program.
Rust frees memory/resources belonging to a value promptly, at a point under your control.
2)Your program will never use a
freed).
Note: C/C++ provide(1)but not(2)
How does Rust give control to programmers over values' lifetimes while keeping
the language safe?It does so by.
These constraints allow Rust'scompile-time checks
of memory safety errors: dangling pointers, double frees, using uninitialized memory, etc
Same rules also form the basis of Rust's support forsafe concurrent programming
Note: Rust does provide C-like,raw pointersbut these can only be used inunsafe code
(dened byunsafe{} blocks)
R. Casadei Intro 18/61

Ownership: intro
Some PLs use GCs
Other PLs require explicit de/allocation of memory.
Rust uses a third approach:memory is managed through a system of
a set of rules that the compiler checks at compile time
Ownership—the idea:theowning objectdecides when to free theowned object
Ownership rules:
1) valuein Rust has aunique
2)When the owner is, the owner value is dropped too .
A variable owns its value. A variable isdropped when it goes out of scope, and its
value is dropped along with it.
­This is similar to
Owners and owned values formtrees
Rust providesexibilityto the ownership model via
IMove )
IAbility to “borrow )
IRef-counted pointer typesRc/Arcthat allow many owners, under some restrictions)
R. Casadei Intro 19/61

Ownership: Move
In Rust, by default (i.e., if a type doesn't impl theCopytrait), operations like assigning a
value to a variable, passing it to a function, or returning it from a function don't copy the
value: they
In Rust, amoveleaves its source uninitialized, as the destination takes ownership
let"udon".to_string(),"ramen".to_string(),"soba".to_string()];
let
//;,,!!!
IMore ops that move
If you assign to a var which was already
initialised, Rustdrops the var's prior
value.
Passing arguments to functions moves
ownership to the function's parameters.
Returning a value from a function
moves ownership to the caller.
Building a tuple moves the values into
the tuple; etc....
R. Casadei Intro 20/61

Ownership: Move
Move examples
letbob".to_string();
s =mark".to_string();bob"
let
s =ste".to_string();s)
fn<i32> {
let
}
letMoves and control ow
let
if
h(x);:
while:,ndMoves and indexed content
let"bob".to_string(),mark".to_string()];
let:note:)
//
letok)
for
s.push(!);
}
println!("{:?}",v);:v
R. Casadei Intro 21/61

Ownership: on copy
Assigning a value of a . The source
of the assignment (or argument passing)remains initialized and usable.
As a general rule, simple scalar values can be Copy, and nothing that requires allocation
or is some form of resource is Copy—e.g.,: all integer types;bools;chars; oating-point
types; tuples or xed-size arrays of only Copy types.
On custom types:By default, struct and enum types are not Copy.
#[derive(Copy,)]
struct
Copy examples
fn) { println!("fs:", s); }
fn){ println!("fi:", i); }
fn::from("!"); s },
dealloced
fnmut) ->"!");
lethello".to_string();
fs(s);s!
//!("s:);s
let
fi(i);
println!("i:", i);
let()
s2 = chg_s(s2)
R. Casadei Intro 22/61

Ownership:RcandArc—shared ownership
Rust providessafe :RcandArc
Rcuses faster non-thread-safe code to update its ref count.
useRc;
let<String> =::new("bob".to_string());
let<String> = s.clone();;
pointer
let<String> = s.clone();
//s<T>
assert!(s.contains("ob"))
A value owned by an Rc pointer isimmutable
üRust's memory and thread-safety guarantees depend on ensuring that no value is ever
simultaneously shared and mutable.
Arcissafe to share between threadsdirectly (“atomic reference count”)
oA well-known problem with using reference counts iscycles(causing memory leaks).
R. Casadei Intro 23/61

Beyond ownership:referencesandborrowing(1/3)
Referencesare
taking ownership of it; i.e., they have no effect on their referents' lifetimes.
Rust refers tocreating a referenceto some value asborrowingthe value: what you
have borrowed, you must eventually return to its owner
fnString) ->
fnmut) { s.push_str("!") }
let::from("bob");
let
chg_s2(&mut
println!("String::", s, len);
Iris a ptr in stack, pointing toshandle in stack (ptr to content in heap + len + capacity)
ISo, passing argsby-valuemoves ownership (of val to fun),by-refmake fun borrowing the val.
Two types of references:
1)Shared ref: typed&T, lets you read but not modify its referent&v; “srefs”are Copy
2)Mutable ref: typed&mut , lets you read or modify its referent&v; “mrefs” are
exclusiveandAREN'T Copy
ÃKinda way to enforce
owners: as long as there are shared refs to a val, not even its owner can modify it)
IThis restriction allows you toavoid data race at compile time.
R. Casadei Intro 24/61

Beyond ownership:referencesandborrowing(2/3)
On dangling refs:in Rust thecompiler guarantees that refs will never be dangling,
by ensuring the data will not go out of scope before the ref to the data does.
fnString{let::from(""); &s}
fn
Rules of references:
num of immutable refs (2) References must always be valid
let
letmutmut
*m += 32;s
structstatic, age:Bob", age: 10 };
let
assert_eq!(r.name,Bob");
assert_eq!((*r).name,Bob");
The.opcan implicitly borrow a ref to its left operand, if needed for a method call.
IRefs of refs:ops like.and==can follows as many refs as needed to nd their targets.
let
v.sort();
(&mut;
Assigning references: assigning a ref makes it point to a new value
let *r == 20);
IIn C++, assigning a ref stores the val in its referent; and you can't change the pointer of a ref.
R. Casadei Intro 25/61

Beyond ownership:referencesandborrowing(3/3)
No null references:Rust references arenever null.
IThere's no analogue to C's NULL or C++'s nullptr; there is no default initial value for a reference
(you can't use any var until it's been initialized, regardless of its type); and Rust won't convert
integers to references (outside of unsafe code).
IIn Rust, if you need a value that is either a reference to something or not, use the type
Option<&T> . At the machine level, None is repr as a null pointer, andSome(r) , whereris a
&Tvalue, as the nonzero address, soOption<&T> is just as efcient as a nullable pointer in C
or C++, but safer.
Borrowing refs to arbitrary expressions
fn) -> *b) }
let
assert_eq!(r + &1009, 1729);
IFor this situations, Rust creates an anonymous var to hold the expr's value and makes a ref
point to that; that var's lifetime depends on what you do with the ref.
Refs to slices and trait objects
IA fat pointer, carrying the starting address of the slice and its length.
IA fat pointer: carries a value's address and a pointer to the
trait's implementation appropriate to that value.
R. Casadei Intro 26/61

Ref safety and lifetimes » borrowing a local var
You can't borrow a ref to a local var and take it out of the var's scope.
{
{
r = &x;
}; assert_eq!(*r,1);dangling)
}
The Rust compiler tries to assign each ref in your program a
IIf you have a varx, then aref toxmust not outlivexitself.
IIf you store a reference in a varr, the reference must be good for the entire lifetime of the var.
eIf you borrow a refrto a part of a data structd, thend's lifetime must encloser's.
let
let
eIf you store a refrin some data structd, thenr's lifetime must enclosed's lifetime.
oOther features introduce other constraints, but the principle is the same: (1) understand
constraints arising from how the program uses refs; (2) nd lifetimes to satisfy them.
R. Casadei Intro 27/61

Ref safety and lifetimes » refs as params
Consider a function that takes a ref and stores it in a global var.
Rust's equivalent of a global var is called astatic: a value created when the program
starts and dropped when it terminates.
Mutable statics are inherently not thread-safe, so you may access them only within an
unsafeblock.
statici32
fni32) {:<a>(p:a)
unsafe:static
required
}
You may read<a>as “for any lifetimea”
Explanation:SinceSTASHlives for the program's entire execution, the reference type it
holds must have a lifetime of the same length; Rust calls this thestaticlifetime. But the
lifetime ofp's reference is somea, which could be anything, as long as it encloses the
call tof. So, Rust rejects our code.
I.e., the only way to ensure we can't leaveSTASHdangling, is to applyfonly to references
to other statics.
Notice how we would need to update the function's signature to make it evident its
behaviour wrt the borrowing parameter.
R. Casadei Intro 28/61

Ref safety and lifetimes » passing refs as args
fn){
let
g(&x);:x
fnstatic) {
h(&x);:x
static
Fromg's signature alone, Rust knows it will not savepanywhere that might outlive the call;
since any lifetime that encloses the call must work fora, Rust chooses the smallest
possible lifetime for&x: that of the call tog
R. Casadei Intro 29/61

Ref safety and lifetimes » returning refs
It's common for a function to take a reference to some data structure, and then return a
reference into some part of that structure.
fni32]) -> &i32<a>(v:ai32])>a
i32...}
let
for *r <*s { s = r; } }
s
}
let
{
let
s = smallest(&parabola);
}
assert_eq!(*s, 0);:
When returning a reference from a function, the lifetime parameter for the return
type must match the lifetime parameter for one of the parameters (otherwise, it'd
refer to a value created within this function, which would be a dangling ref, or static
data!).
Argument&parabolamust not outliveparabolaitself; yetsmallest's return value must
live at least as long ass. There's no possible lifetimeathat can satisfy both constraints,
so Rust rejects the code.
R. Casadei Intro 30/61

Ref safety and lifetimes » structs containing refs
In structs, a eld of ref type (or type parametric on lifetime)must also specify the lifetime
structi32:r:i32
structjust)
r: &aa
//a
}a
struct:s:
structstatic> }
structs
holds
let
{
let
s = S2 { r: &x };:
}
assert_eq!(*s.r, 10);,
On lifetimes
It's not just references and struct types that have lifetimes. Every type in Rust has a
lifetime, includingi32andString. Most are simply 'static , meaning that values of those
types can live for as long as you like; e.g., a Vec<i32> is self-contained, and needn't be
dropped before any particular variable goes out of scope. But a type likeVec<&a>
has a lifetime that must be enclosed bya: it must be dropped while its referents are still
alive
R. Casadei Intro 31/61

Ref safety and lifetimes » Distinct lifetime parameters
struct, y: &a
let
let
{
let
{
letss
r = s.x;.x.y.y
}
}:
//
struct
x: &a,
y: &b
}
With the last def,s.xands.yhave independent lifetimes. What we do withs.xhas no
effect on what we store ins.y, so it's easy to satisfy the constraints now:acan simply be
r's lifetime, andbcan bes's. (y's lifetime would work too forb, but Rust tries to choose
the smallest lifetime that works.)
R. Casadei Intro 32/61

Sharing vs. mutation
Other situations where Rust protect us against dangling pointers
let,
let
let:v
r[0];vaside,
IAcross its lifetime, ashared ref makes its referent read-only.
fnmut<f64>, slice: &[f64]){ *e); }}
let::new();
extend(&mut
extend(&mut:t
IHere, Rust protects us against a slice turning into a dangling pointer by a vector reallocation
R. Casadei Intro 33/61

Sharing vs. mutation
Rust's rules for mutation and sharing:
1)Shared access is read-only access: values borrowed by shared refs are RO
2)Mutable access is exclusive access: a value borrowed by a mutable ref is reachable
exclusively via that reference
Each kind of reference affects what we can do with(i) the values along the owning path
to the referent, and (ii) the values reachable from the referent.
let
letmut
v = (8,9);:v
let:v.
letmut
*m = (8,9);: *ms
println!("{}",v.1);:v.1...
R. Casadei Intro 34/61

Rust makes it difcult to build a “sea of objects”
Since the rise of automatic memory management, the default architecture of all programs
has been the, with many objects related by intricate dependencies
Rust, with its ownership model, fosters the constructions of
I.e., Rust makes it hard to build cycles (two values such that each one contains a
reference to the other)
IYou need to use smart pointers like Rc and interior mutability
You'll have a hard time in recreating all OOP antipatterns: Rust's ownership model will
give you some trouble; the cure is to do some up-front design and build a better program
Rust is all about transferring the pain of understanding your program from the future to the
present.
ÃIt works unreasonably well: not only can Rust force you to understand why your program
is thread-safe, it can even require some amount of high-level architectural design
R. Casadei Intro 35/61

Outline
1
Intro
2
Ownership, borrows, moves
3
UDTs
4
More
R. Casadei Intro 36/61

Structs
Three kinds of struct types:
Named-eld Structs
pub,,
bool, }
let;
let::from("abc"),
field2: 88, field3 };
let, ..st1};
//:t
IOn ownership of struct data: noticeMyStructuses owned String type rather than&str
string slice type: indeed, we want struct instances to own all of its data .
Tuple structshave no names for elds
structi32,,);:
let
println!("x={};={}", origin.0, origin.1);
IGood fornewtypes: structs with a single element that you def to get stricter type checking
Convention:CamelCasefor types;snake_casefor elds and methods
Adding useful functionality with derived traits
#[derive(Debug)] struct, field2:, field3:,}
fn"{:?}",s) };
ÃCommon traits include: Copy, Clone, Debug, PartialEq, PartialCmp
R. Casadei Intro 37/61

Methods and associated functions
Methodsarefns dened in the context of a struct/enum/trait, which take thereceiver as
rst parameter with special nameself(you can omit the type)
Static methodsare methods that don't takeselfas parameter
Terminology: are items (e.g., functions) associated with a type.
Each struct can havemultipleimplblocksfor dening methods/associated functions.
#[derive(Debug)] struct, height:
impl
fnself) -> (f64,f64) { (self.width,.height) }
fnself) ->.width *self.height }
fnmut, perc:) {
}
impl
fnsize:) ->, height:
}
let
let
let
let;
IJust like functions, methods can take ownership of “self” or borrow it im/mutably.
Method call—Note: Rust doesn't have an equivalent to the->operator in C++; instead, it
provides
R. Casadei Intro 38/61

Generic structs
Generic structs: accept type parameters
pub<T> { older:<T>, younger:<T> }
impl<T><T> {
pub<T>
Queue::new(), younger:::new() }
}
pubmut, t: T) {.younger.push(t); }
}
For static method calls, you can supply the type parameter explicitly using the turbosh
::<>notation:
let::<char>::new();
But in practice, you can usually just let Rust gure it out for you
let::new();
q.push("CAD");<&static>
R. Casadei Intro 39/61

Enums
While structs are “AND” (product) types, enums are “OR” (sum) types.
Algebraic Data Types (union types)andpattern matchingandif-let
#[derive(Debug,Copy)] enum
Quit,corresp.)
Move { x:, y:
Write(String),
ChangeColor(i32,i32,i32,i32),
}
impl,
fnself) -> &static
match...", *...*/
} }
letWrite(String::from("hello"));
ifWrite(theMsg) = &m { println!("if-let") }m
letm
Msg::Write(s) => s, _ =>::from("dunno")
};
Enums are usefulwhenever a value might be either one thing or another; the “price” of
using them is that you must access the data safely, usingpattern matching.
In memory:enums with data are stored as a small integer, plus enough memory
to hold all the elds of the largest variant
oEnums provide safety for exibility: end users of an enum can't extend it to add new
variants; variants can be added only by changing the enum declaration (and when that
happens, existing code breaks—new variants must be dealt with via newmatcharms).
R. Casadei Intro 40/61

Traits
Atraitis a feature that any given type may or may not support (typeclass)
A value that implsstd::io::Writecan write out bytes
A value that implsstd::iter::iterator can produce a seq of values
trait
fnmut, buf: &[u8]) -><usize>;
fnmut) -><()>;
fnmut, buf: &[u8]) -><()> { *...*/
//...
}
useWrite;
fnmut) -> std::io::Result<()> {
out.write_all(b"hello\n")?;
out.flush()
}muta"
letFile::create("hello.txt")?;
say_hello(&mut
let
say_hello(&mut
assert_eq!(bytes, b"hello\n");
fn>(v1: T, v2: T) -> T {
//T:>this
Ord"
oAs can extend any type, a trait must be in scope to be used.
ITraits like e.g.Clone, are in std prelude and hence automatically imported in any module.
R. Casadei Intro 41/61

Trait objects: references to traits
You can't have vars typed Write:a var's size must be known at compile time, and
types that impl Write can be any size.
useWrite;
let<u8> = vec![];
let:Write
Instead, you need atrait object, i.e., areference to a trait type.
letmutmut
Trait object layout:
representing that value's type(so it takes up to 2 machine words)
In Rust, as in C++, thevtableis generated once
(statically) and shared by all objects of the same type.
While C++ storesvptras part of the struct, Rust uses
fat pointers
ISo, a struct can impl many traits w/o containing many
vptrs.
Rust automatically converts ordinary refs into trait
objects (or among fat pointers, e.g., fromBox<File>
toBox<Write> ) when needed.
R. Casadei Intro 42/61

Generic functionsandbounds
fnmut) -><()> {
fn>(o: &mut<()> { o.write_all(b"Hello\n")?; o.flush();
}
hello_gen(&mut::<File>
hello_gen(&mut::<Vec<u8>>
Wis atype parameter; withboundW: it stands for some type that implsWrite.
Rust generates machine code for concrete instantiations of generic functions, e.g.,
hello_gen::<File>() that calls appropriateFile::write_all() andFile::flush() .
If we need multiple abilities from a type parameter, we can “concatenate” bounds with+
fn>(values: &Vec<T>) {
No bounds:you can't do much such a val: you can move it, put it into a box/vec.. That's it.
Multiple parameter types:
fn
data: &DataSet, map: M, reduce: R) -> Results {
//
fn
where
Lifetime parameters: these come rst
fn...}
R. Casadei Intro 43/61

Trait objects vs. generic code
Trait objects:
Trait objects are the right choice whenever you need a collection of values of mixed types,
all together.
trait
struct<V> }
vegetables
struct<Vegetable> }:Vegetablet
constant
struct<Box<Vegetable>> }
IEachBox<Vegetable> can own any type of vegetable, but the box itself has a constant size
(two pointers).
Another possible reason to use trait objects is toreduce the total amount of compiled
code. Rust may have to compile a generic function many times, once for each type it's
used with.
Generics:
Speed. Rust compiler generates machine code for generic functions, it knows the types
it's working with and hence which methods to call: so there's no dynamic dispatch (unlike
with trait objects). Inlining is possible, calls with consts can be evaluated at compile time
etc.
Moreover, not every trait can support trait objects.
R. Casadei Intro 44/61

Traits: denition/impl
Trait declaration and implementation
trait
fnself);
fnself){.m();.m(); }
}
impl
fnself){ *...*/
}
Extension trait: trait created with the sole purpose of adding a method to existing types.
You can even use a genericimplblock to impl a trait for a whole family of types at once.
impl> SomeTrait
Coherence rule: when you impl a trait, the trait or type must be new in current crate
IIt helps Rust ensure that trait implementations are unique.
Subtrait—if traitBextends traitA, then allBvalues are alsoAvalues
ISo, any type that implsBmust also implA
trait
impl
impl
R. Casadei Intro 45/61

Traits: denition/impl
Self: a trait can use keywordSelfas a type
pubself):; *...*/
pubself, other: &Self) ->; *...*/
oA trait that uses theSelftype is incompatible with trait objects.
//:Spliceable
fn
let
Rust rejects this code cause it has no way to typecheck calla.splice(b)
IThe whole point of trait objects is that the type isn't known until runtime. Rust has no
way to know at compile time if left and right will be the same type, as required.
IThe more advanced features of traits are useful,but they can't coexist with trait objects
because with trait objects, you lose the type info Rust needs to type-check your program.
ÃA trait-object-friendly trait for splicing must not useSelf:
pub
fnself, other: &MegaSpliceable) -><MegaSpliceable>;
}
oTrait objects don't support static methods: if you want to use&StringSettrait objects, you
must excuse these by adding boundwhere:
trait
fn:;
R. Casadei Intro 46/61

Fully Qualied Method Calls
A
"hello".to_string()
//
str::to_string("hello")
//
ToString::to_string("hello")
ToString
//
<str>::to_string("hello")
All but the rst arequaliedmethod calls: they specify the type or trait that the method is
associated with. The last one isfully qualied
R. Casadei Intro 47/61

Traits that dene relationships between types
Associated types (or How iterators work)
pubs
types..
fnmut) -><Self::Item>;so::item
}
impl::env::args()
type;
fnmut) -><String> {
Associated typesare types specied by trait implementations
Generic code can use associated types
fn>(iter: I) -><I::Item> {
let::new();
for
res
}
fn, I::Item:
for"{}:", i, v); } }
//:<I>(it:):<Item=String>
IIf you think ofIteratoras the set of all iterator types, thenIterator<Item=String> is a
subset ofIterator: the set of iterator types that produceStrings
ÃTraits with associated types, likeIterator, are compatible with trait objects, but only if all
the associated types are spelled out.
R. Casadei Intro 48/61

Traits that dene relationships between types
Generic traits (or How operator overloading works)
In Rust, traitstd::ops::Mulis for types that support multiplication*
pub<RHS=Self> {
type; *
fnself, rhs: RHS) ->::Output; *
}
impl.:<Complex>
fnMul>(){.:<M:Mul<M>>(){
Buddy traits (or How rand::random() works)
“Buddy traits”are traits designed to work together
pub
fnmut) ->;
//
}
pub
fnmut;
}:
let::rand(rng);
Another example is traitHashfor hashable types and traitHasherfor hashing algorithms.
R. Casadei Intro 49/61

Reverse-engineering bounds
Consider the following and suppose we want to make it generic to also support oats.
fni64], v2: &[i64]) ->
let *v2[i];
}; tot }
With a boundless type parameterN, Rust would complain for uses of+,*and0
You can introduce boundT:+Mul+Default
oThis still wouldn't work, becausetotal=total+v1[i]*v2[i]assumes that multiplying two
values of typeNyields anotherN(which isn't generally the case)úYou need to be
specic about the output
fn<Output=N> +<Output=N> +>(v1: &[N], v2: &[N]) -
> N {..}
//[E0508]:N],
//[i] *v2[i];
//
But it still complains: it would be illegal to movev1[i]out of the slice, but numbers are
copyable, so what's the problem? The point is that Rust doesn't knowv1[i]is a number
and hence copyable. So the nal, working bound is
where<Output=N> +<Output=N> +
üCratenumdenes aNumtrait that would allow us to simplify the bound into
where
R. Casadei Intro 50/61

Rust generics vs. C++ templates
One advantage of Rust approach vs. C++ templates (where constraints are left implicit in
the code, à la duck typing) isforward compatibility of generic code(as long as you
don't change the signature, you're ne)
Another benet islegibility and documentation
Moreover, C++ compiler error messages involving templates can be much longer than
Rust's, pointing at many different lines of code, because the compiler has no way to tell
who's to blame for a problem: the template, its caller (which might also be a template), or
thattemplate's caller.
R. Casadei Intro 51/61

Traits for operator overloading
Traits for operator overloading are dened understd::ops
Unary ops:-x(Neg,!x(Not)
Arithmetic ops:+,-,*,/,%(Add,Sub,Mul,Div,Rem)
Bitwise ops:&,|,^,<<,>>(BitAnd,BitOr,BitXor,Shl,Shr)
Compound assignment/arithmetic ops:x+=y,... (AddAssign,SubAssign, ...)
Compound assignment/bitwise ops:x&=y,... (BitAndAssign, ...)
Indexing:x[y],&x[y](Index),x[y]=z, &mut (IndexMut)
Traits for comparison operator overloading are def understd::cmp
Comparison:x==y, x!=y(PartialEq),x<y,... (PartialOrd)
R. Casadei Intro 52/61

Traits for operator overloading
Example: Add
trait<RHS=Self> {::ops::Add
type;
fnself, rhs: RHS) ->::Output;
}
//+b.add(b)
useAdd;.add(b)
assert_eq!(4.124f32.add(5.75), 9.875);Example: operators for Complex
#[derive(Clone,Copy,Debug)] struct
impl<T><Output=T> {
type;
fnself, rhs:): ->self.re+rhs.re, im:self.im+rhs.im }}
}
//
impl<L, R, O><Complex<R>><R,=O> {
type
fnself, rhs: Complex<R>) ->::Output
},
impl<T><T> {
fnmut, rhs: Complex<T>){.re+=rhs.re;.im+=rhs.im; }
}
R. Casadei Intro 53/61

Closures » Types and Safety
Function vs. closure type
Function type:fn(ArgT1,ArgT2,...)->RetType
IReturn type is optional: if omitted, it's()
Closure type:since a closure may contain data, every closure has its own, ad-hoc type
created by the compiler, large enough to hold that data. Not two closures have exactly the
same type,but every closure impls traitFn
To write a function that accepts a function or closure, you've to use aFnbound
fn(&Vec<i32>) ->
bool
Closures and safety
Most of the story is simply that when a closure is created, it either moves or borrows
the captured variables.
However, some consequences are not obvious, or what happens when a closure drops or
modies a captured value.
R. Casadei Intro 54/61

Closures » Types and Safety
Closures dropping values
lethello".to_string();
let
//(my_str);:
f();
f();:NOTE:!)
Rust knows the above closurefcan't be called twice
fn() { closure(); closure(); }
lethello".to_string();
call_twice(f);:Fn,
//FnOnce
Closures that drop values, likef, are not allowed to haveFn: they impl a less
powerful trait: the trait of closures that can be called once .
Closures containing mutable data ormutreferences
FnMut (they, e.g., are not safe to call from multiple threads)
let
let *borrows */; println!("i", i); };
call_twice(inc);:<F:FnMut()>(mut:)...}
R. Casadei Intro 55/61

Outline
1
Intro
2
Ownership, borrows, moves
3
UDTs
4
More
R. Casadei Intro 56/61

Iterators
Aniteratoris any value that impls traitstd::iter::Iterator
trait
type
fnmut) -><Self::item>;
//:,,
}
If there's a natural way to iterate over a type, the type can impl
std::iter::IntoIterator and we call such type aniterable
trait::IntoIter::Item ==::Item {
type
type:;
fnself) ->::IntoIter;
}
The stdlib provides a blanket impl ofIntoIteratorfor every type that implsIterator.
Under-the-hood, every for loop is just syntactic sugar over IntoIterator/Iterator methods:
let"antimony",arsenic",aluminum",selenium"];
for"{}", el); }
//:
let
while(el) = iterator.next() { println!("{}", el); }
R. Casadei Intro 57/61

Concurrency example
Goal: unifying iterator pipelines and thread pipelines
documents.into_iter()
.map(read_whole_file)
.errors_to(error_sender)
.off_thread()
.map(make_single_file_index)
.off_thread()
...........
use,
pubself) -> mpsc::IntoIter<Self::Item>; }
impl<T> OffThreadExt+Send+static, T::Item:+static
fnself) -> mpsc::IntoIter<Self::Item> {
let sync_channel(1024);
spawn(move
for send(item).is_err() {; } }
});
recvr.into_iter()
} }
Channelsare one-way conduits for sending values from a thread to another
Sync channelssupport backpressure by blockingsend()s if the channel is full
Vals ofSendtypes are safe to be passed as values (i.e. moved) across threads
mpsc::IntoIter(created byReceiver::into_iter ) is an iterator over msgs on a
Receiver, which block whenevernextis called, waiting for a new msg.
R. Casadei Intro 58/61

Macros
Goal
//
#[derive(Clone,PartialEq,Debug)] enumbool), Num(f64),
Str(String), Arr(Vec<Json>), Obj(Box<HashMap<String,Json>>)
//...
let
{name":Bob",class_of": 1926,major":"CS"
{name":Steve",class_of": 1933,major":"Ph"
]);
//...
letBox::new(.............
R. Casadei Intro 59/61

Macros
Basic solution
macro_rules! json {
(null) => {pattern)template)
Json::Null
};
([ $( $elem:tt ),*]) => {
Json::Array(vec![ $( json!($elem) ), *])!
};
({ $( $key:tt : $value:tt ), *}) => {
Json::Object(Box::new(vec![ $( ($key.to_string(), json!($value)) ), *]
.into_iter().collect()))
};
($other:tt) => {tttoken)
Json::from($other)/number/string
};
}
macro_rules! impl_from_num_for_json {
( $( $t:ident )*) => {
$(
impl<$t>
fn) }
}
)*
};
}
impl_from_num_for_json!(u8);
R. Casadei Intro 60/61

References
[1] Programming Rust: Fast, Safe Systems Development.
2017.ISBN: 9781491927236.URL:https://books.google.es/books?id=hcc\_DwAAQBAJ .
R. Casadei Appendix 61/61