"Rust is a systems programming language that runs blazingly fast, prevents nearly all segfaults, and guarantees thread safety." – prev.rust-lang.org
"Empowering everyone to build reliable and efficient software. " – rust-lang.org
Rust is:
cargo
: one of the best package managers/build systems in class. Say no to dependency hell and makefiles!rustdoc
).clippy
: A LOT of additionall lints.rustfmt
: code formatting utility.Date | Stable | Beta | Nightly |
---|---|---|---|
2018-09-13 | 🚂 1.29 | 🚆 1.30 | 🚝 1.31 |
2016-10-25 | 🚆 1.30 | 🚝 1.31 | 🚈 1.32 |
2016-12-06 | 🚝 1.31 | 🚈 1.32 | 🚅 1.33 |
Hello, Rust!
fn main() { println!("Hello, world!");}
Variables are bound with let
:
let x = 17;
Bindings are implicitly-typed: the compiler infers based on context.
The compiler can't always determine the type of a variable, so sometimes you have to add type annotations.
let x: i16 = 17;
Variables are inherently immutable:
let x = 5;x += 1; // error: re-assignment of immutable variable xlet mut y = 5;y += 1; // OK!
Bindings may be shadowed:
let x = 17;let y = 53;let x = "Shadowed!";// x is not mutable, but we're able to re-bind it
The shadowed binding for x
above lasts until it goes out of scope.
Above, we've effectively lost the first binding, since both x
s are in the same scope.
Patterns may also be used to declare variables:
let (a, b) = ("foo", 12);let [c, d] = [1, 2];
()
.()
has only one value: ()
.()
is the default return type.()
.()
.fn foo() -> i32 { 5 }fn bar() -> () { () }fn baz() -> () { 5; }fn qux() { 5; }
Because everything is an expression, we can bind many things to variable names:
let x = -5;let y = if x > 0 { "greater" } else { "less" };println!("x = {} is {} than zero", x, y);
Side note: "{}"
is Rust's (most basic) string interpolation operator
printf
's "%s"
in C/C++."{:?}"
can be used for debug formatting//! Comments like this are for module/crate documentation./// Triple-slash comments are docstring comments.////// `rustdoc` uses docstring comments to generate/// documentation, and supports **Markdown** formatting.fn foo() { // Double-slash comments are normal. /* Block comments also exist /* and can be nested! */ */}
bool
: spelled true
and false
.char
: spelled like 'c'
or '😺'
(char
s are Unicode code-points, i.e. 4 bytes long!).
Numerics: specify the signedness and size.
i8
, i16
, i32
, i64
, isize
u8
, u16
, u32
, u64
, usize
f32
, f64
isize
& usize
are the size of pointers (and therefore have
machine-dependent size)10i8
, 10u16
, 10.0f32
, 10usize
.i32
or f64
:10
defaults to i32
, 10.0
defaults to f64
.Arrays, slices, str
, tuples.
[T; N]
.[]
like most other languages:arr[3]
gives you the 4th element of arr
let arr1 = [1, 2, 3]; // (array of 3 elements)let arr2 = [2; 32]; // (array of 32 `2`s)
&[T]
&mut [T]
or immutable &[T]
let arr = [0, 1, 2, 3, 4, 5];let val = arr[0]; // val = 0let total_slice = &arr; // Slice all of `arr`let total_slice = &arr[..]; // Same, but more explicitlet partial_slice = &arr[2..5]; // [2, 3, 4]
String
and &str
.String
is a heap-allocated, growable vector of characters.&str
is a type¹ that's used to slice into String
s."foo"
are of type &str
.let s: &str = "galaxy";let s2: String = "галактика".to_string();let s3: String = String::from("銀漢");let s4: &str = &s3;
let s1 = "foobar";// You can slice strings:let s2 = &s1[1..3];// But you can't index with []// you can use `s1.chars().nth(1).unwrap()` insteadlet s3 = s1[1] // does not work!
¹str
is an unsized type, which doesn't have a compile-time known size,
and therefore cannot exist by itself.
foo.0
, foo.1
, etc.let
bindingslet foo: (i32, char, f64) = (72, 'H', 5.1);let (x, y, z) = (72, 'H', 5.1);let (a, b, c) = foo; // a = 72, b = 'H', c = 5.1
as
:let x: i32 = 100;let y: u32 = x as u32;
[i16; 4]
to char
! (This is called a "non-scalar" cast)Vec<T>
Vec
(read "vector") is a heap-allocated growable array.ArrayList
, C++'s std::vector
, etc.)<T>
denotes a generic type.Vec
of i32
s is Vec<i32>
.Vec
s with Vec::new()
or the vec!
macro.Vec::new()
is an example of namespacing. new
is a function defined for
the Vec
struct.Vec<T>
// Explicit typinglet v0: Vec<i32> = Vec::new();// v1 and v2 are equallet mut v1 = Vec::new();v1.push(1);v1.push(2);v1.push(3);let v2 = vec![1, 2, 3];
// v3 and v4 are equallet v3 = vec![0; 4];let v4 = vec![0, 0, 0, 0];
Vec<T>
let v2 = vec![1, 2, 3];let x = v2[2]; // 3
Like arrays and slices, vectors can be indexed and sliced with []
.
usize
because usize
is guaranteed to be the same size as a pointer.usize
:let i: i8 = 2;let y = v2[i as usize];
Vectors has an extensive stdlib method list, which can be found in the offical Rust documentation.
&
: &i32
.&
(like C/C++).*
(like C/C++).let x = 12;let ref_x = &x;println!("{}", *ref_x); // 12
if x > 0 { 10} else if x == 0 { 0} else { println!("Not greater than zero!"); -10}
()
:if x <= 0 { println!("Too small!");}
Loops come in three flavors: while
, loop
, and for
.
break
and continue
exist just like in most languageswhile
works just like you'd expect:
let mut x = 0;while x < 100 { x += 1; println!("x: {}", x);}
loop
is equivalent to while true
, a common pattern.let mut x = 0;loop { x += 1; println!("x: {}", x);}
for
is the most different from most C-like languagesfor
loops use an iterator expression:n..m
creates an iterator from n to m (exclusive).Vec
s.// Loops from 0 to 9.for x in 0..10 { println!("{}", x);}let xs = [0, 1, 2, 3, 4];// Loop through elements in a slice of `xs`.for x in &xs { println!("{}", x);}
fn foo(x: T, y: U, z: V) -> T { // ...}
foo
is a function that takes three parameters:x
of type T
y
of type U
z
of type V
foo
returns a T
.
Must explicitly define argument and return types.
return
for early returns from a function.fn square(n: i32) -> i32 { n * n}fn squareish(n: i32) -> i32 { if n < 5 { return n; } n * n}fn square_bad(n: i32) -> i32 { n * n;}
()
.let x: fn(i32) -> i32 = square;
fn apply_twice(f: &Fn(i32) -> i32, x: i32) -> i32 { f(f(x))}// ...let y = apply_twice(&square, 5);
!
at the end.macro_rules! macro_name
blocks.print!
& println!
{}
for general string interpolation, and {:?}
for debug printing.{:?}
, like arrays and Vec
s.print!("{}, {}, {}", "foo", 3, true);// => foo, 3, trueprintln!("{:?}, {:?}", "foo", [1, 2, 3]);// => "foo", [1, 2, 3]
format!
println!
-style string interpolation to create formatted String
s.let fmted = format!("{}, {:x}, {:?}", 12, 155, Some("Hello"));// fmted == "12, 9b, Some("Hello")"
panic!(msg)
if x < 0 { panic!("Oh noes!");}
assert!
& assert_eq!
assert!(condition)
panics if condition
is false
.assert_eq!(left, right)
panics if left != right
.debug_assert!(condition)
and debug_assert_eq!(left, right)
work in debug build, but omitted in release build.#[test]fn test_something() { let actual = 1 + 2; assert!(actual == 3); assert_eq!(3, actual);}
unreachable!()
panic!
s when reached.if false { unreachable!();}
unimplemented!()
panic!("not yet implemented")
.fn sum(x: Vec<i32>) -> i32 { // TODO unimplemented!();}
dbg!()
let a = 2;let b = dbg!(a * 2) + 1;// ^-- prints: [src/main.rs:2] a * 2 = 4assert_eq!(b, 5);
switch
on steroids.let x = 3;match x { 1 => println!("one fish"), // <- comma required 2 => { println!("two fish"); println!("two fish"); }, // <- comma optional when using braces // we can match several patterns in one arm 3 | 4 => println!("three or four, dunno"), _ => println!("no fish for you"), // "otherwise" case}
match
takes an expression (x
) and branches on a list of value => expression
statements.if
, all arms must evaluate to the same type._
is commonly used as a catch-all (cf. Haskell, OCaml).let x = 3;let y = -3;let q = 10;let s = match (x, y) { (1, 1) => "one".to_string(), (2, j) => format!("two, {}", j), (_, 3) => "three".to_string(), (i, j) if i > 5 && j < 0 => "On guard!".to_string(), // NB: note that we do not test x == 10 here! (q, k) => format!("???: {} {}", q, k),};println!("{}", s);
_
is a throw-away variable name.if
-guards to constrain a match to certain conditions.for
loop without recursion.match
.F(0) = 0; F(1) = 1; F(n) = F(n - 1) + F(n - 2)
fn fibonacci(n: u32) -> u64 { if n == 0 { return 0; } let mut sum = 1; let mut last = 0; let mut curr = 1; for _ in 1..n { sum = last + curr; last = curr; curr = sum; } sum}
fn fibonacci(n: u32) -> u64 { match n { 0 => 0, 1 | 2 => 1, 3 => 2, // 50 => 12586269025, _ => fibonacci(n - 1) + fibonacci(n - 2), }}
rustc
.rustc your_program.rs
to compile into an executable your_program
.rustc
doesn't need to be called once for each file like in C.main.rs
or lib.rs
).cargo
, Rust's package manager and build system.cargo new project_name
(executable)cargo new project_name --lib
(library)cargo build
cargo run
--release
flag to enable release build profile (longer compilation times, but more performant binaries)cargo check
(much faster)cargo bench
(requires Nightly toolchain)cargo test
Cargo.toml
file to declare and manage dependencies and
project metadata.[package]name = "my_cool_project"version = "0.1.0"authors = ["My name"]edition = "2018"[dependencies]uuid = "0.1"rand = "0.3"[profile.release]opt-level = 3debug = false
cargo test
#[test]
.cargo test
will run all annotated functions in your project.panic!
ing) succeeds.assert!
(or assert_eq!
) to check conditions (and panic!
on failure)#[test]fn it_works() { // ...}
fn foo() { // Creates a Vec object. // Gives ownership of the Vec object to v1. let mut v1 = vec![1, 2, 3]; v1.pop(); v1.push(4); // At the end of the scope, v1 goes out of scope. // v1 still owns the Vec object, so it can be cleaned up.}
So here are the basics.
let v1 = vec![1, 2, 3];// Ownership of the Vec object moves to v2.let v2 = v1;println!("{}", v1[2]); // error: use of moved value `v1`
let v2 = v1;
v2
, and declare v1
invalid.println!("{}", v1[2]);
v1
is no longer a valid variable binding, so this is an error.Vec<T>
), we do
not touch data on heap, just few bytes allocated on stack are copied
(pointer to heap, length and capacity; 24 bytes on 64-bit machine)std::move
.std::mem::replace
in Rust to provide
advanced ownership management.std::mem::replace
, std::mem::swap
and others.fn vector_length(v: Vec<i32>) -> Vec<i32> { // Do whatever here, // then return ownership of `v` back to the caller}
let v = vec![1, 2, 3];// v_ref is a reference to v.let v_ref = &v;// use v_ref to access the data in the vector v.assert_eq!(v[1], v_ref[1]);
let v = vec![1, 2, 3];// v_ref is a reference to v.let v_ref = &v;// Moving ownership to v_new would invalidate v_ref.// error: cannot move out of `v` because it is borrowedlet v_new = v;
/// `length` only needs `vector` temporarily, so it is borrowed.fn length(vec_ref: &Vec<i32>) -> usize { // vec_ref is auto-dereferenced when you call methods on it. vec_ref.len() // you can also explicitly dereference. // (*vec_ref).len()}fn main() { let vector = vec![]; length(&vector); println!("{:?}", vector); // this is fine}
length
: vec_ref
is passed by reference, so it's now an &Vec<i32>
.length
)./// `push` needs to modify `vector` so it is borrowed mutably.fn push(vec_ref: &mut Vec<i32>, x: i32) { vec_ref[0] = 100; vec_ref.push(x);}fn main() { let mut vector: Vec<i32> = vec![1, 2]; let vector_ref: &mut Vec<i32> = &mut vector; push(vector_ref, 4); assert_eq!(vector_ref, &[100, 2, 4]);}
&mut vec_ref
.vec_ref
is a reference to a mutable Vec
.&mut Vec<i32>
, not &Vec<i32>
./// `push` needs to modify `vector` so it is borrowed mutably.fn push2(vec_ref: &mut Vec<i32>, x: i32) { // error: cannot move out of borrowed content. let vector = *vec_ref; vector.push(x);}fn main() { let mut vector = vec![]; push2(&mut vector, 4);}
vec_ref
into a variable binding because that
would change the ownership of the data./// `length` only needs `vector` temporarily, so it is borrowed.fn length(vec_ref: &&Vec<i32>) -> usize { // vec_ref is auto-dereferenced when you call methods on it. vec_ref.len()}fn main() { let vector = vec![]; length(&&&&&&&&&&&&vector);}
let mut a = 5;let ref_a = &mut a;*ref_a = 4;println!("{}", *ref_a + 4);// ==> 8
Copy
TypesCopy
that signifies that a type may be
copied instead whenever it would be moved.Copy
(i32
, f64
, char
, bool
, etc.)Copy
(e.g. Vec
, String
).let x: i32 = 12;let y = x; // `i32` is `Copy`, so it's not moved :Dprintln!("x still works: {}, and so does y: {}", x, y);
¹ Like a Java/Go interface or Haskell typeclass
Learn these rules, and they will serve you well.
&T
).&mut T
) (not both).ConcurrentModificationException
(at runtime!)let mut vs = vec![1,2,3,4];for v in &vs { vs.pop(); // ERROR: cannot borrow `vs` as mutable because // it is also borrowed as immutable}
pop
needs to borrow vs
as mutable in order to modify the data.vs
is being borrowed as immutable by the loop!let y: &i32;{ let x = 5; y = &x; // error: `x` does not live long enough}println!("{}", *y);
error: `x` does not live long enoughnote: reference must be valid for the block suffix following statement 0 at 1:16...but borrowed value is only valid for the block suffix following statement 0 at 4:18
Vec
s in three different ways:let mut vs = vec![0,1,2,3,4,5,6];// Borrow immutablyfor v in &vs { // Can also write `for v in vs.iter()` println!("I'm borrowing {}.", v);}// Borrow mutablyfor v in &mut vs { // Can also write `for v in vs.iter_mut()` *v = *v + 1; println!("I'm mutably borrowing {}.", v);}// Take ownership of the whole vectorfor v in vs { // Can also write `for v in vs.into_iter()` println!("I now own {}! AHAHAHAHA!", v);}// `vs` is no longer valid
Rust has two simple ways of creating structured data types:
Structs and enums may have one or more implementation blocks (impl
s) which
define methods for the data type.
name: type
.struct Point { x: i32, y: i32,}
CamelCase
names, and their fields have snake_case
names.let origin = Point { x: 0, y: 0 };
let mut p = Point { x: 19, y: 8 };p.x += 1;p.y -= 1;
Cell
types.struct Point { x: i32, mut y: i32, // Illegal!}
Point
is foo::Point
.pub
keyword.mod foo { pub struct Point { pub x: i32, y: i32, }}fn main() { let b = foo::Point { x: 12, y: 12 }; // ^~~~~~~~~~~~~~~~~~~~~~~~~~~ // error: field `y` of struct `foo::Point` is private}
mod foo { pub struct Point { pub x: i32, y: i32, } // Creates and returns a new point pub fn new(x: i32, y: i32) -> Point { Point { x: x, y: y } }}
new
is inside the same module as Point
, so accessing private fields is
allowed.match
ingmatch
statements.pub struct Point { x: i32, y: i32,}match p { Point { x, y } => println!("({}, {})", x, y)}
match
ingmatch
es:match p { Point { y: y1, x: x1 } => println!("({}, {})", x1, y1)}match p { Point { y, .. } => println!("{}", y)}
struct_field: new_var_binding
to change the variable it's bound to...
to ignore all unnamed fields... s
to copy some or all fields from s
.struct Foo { a: i32, b: i32, c: i32, d: i32, e: i32 }let mut x = Foo { a: 1, b: 1, c: 2, d: 2, e: 3 };let x2 = Foo { e: 4, .. x };// Useful to update multiple fields of the same struct:x = Foo { a: 2, b: 2, e: 2, .. x };
x.0
, x.1
, etc).match
these.struct Color(i32, i32, i32);let mut c = Color(0, 255, 255);c.0 = 255;match c { Color(r, g, b) => println!("({}, {}, {})", r, g, b)}
// Not equatablestruct Meters(i32);struct Yards(i32);// May be compared using `==`, added with `+`, etc.type MetersAlias = i32;type YardsAlias = i32;
struct Unit;let u = Unit;
enum Resultish { Ok, Warning { code: i32, message: String }, Err(String)}
Resultish::Ok
.use Resultish::*
.match make_request() { Resultish::Ok => println!("Success!"), Resultish::Warning { code, message } => println!("Warning: {}!", message), Resultish::Err(s) => println!("Failed with error: {}", s),}
Resultish::Ok
and the like can be used as functions.List
type:enum List { Nil, Cons(i32, List),}
box
?enum List { Nil, Cons(i32, List),}// error: invalid recursive enum type// help: wrap the inner value in a box to make it representable
box
(lowercase) is a general term for one of Rust's ways of allocating data on the heap.Box<T>
(uppercase) is a heap pointer with exactly one owner.Box
owns its data (the T
) uniquely-- it can't be aliased.Box
es are automatically destructed when they go out of scope.Box
with Box::new()
:let boxed_five = Box::new(5);enum List { Nil, Cons(i32, Box<List>), // OK!}
impl Point { pub fn distance(&self, other: Point) -> f32 { let (dx, dy) = (self.x - other.x, self.y - other.y); ((dx.pow(2) + dy.pow(2)) as f32).sqrt() }}fn main() { let p = Point { x: 1, y: 2 }; p.distance();}
impl
block.pub
.impl
blocks themselves don't need to be made pub
.self
, determines what kind of ownership the method
requires.&self
: the method borrows the value.&mut self
: the method mutably borrows the value.self
: the method takes ownership.impl Point { fn distance(&self, other: Point) -> f32 { let (dx, dy) = (self.x - other.x, self.y - other.y); ((dx.pow(2) + dy.pow(2)) as f32).sqrt() } fn translate(&mut self, x: i32, y: i32) { self.x += x; self.y += y; } fn mirror_y(self) -> Point { Point { x: -self.x, y: self.y } }}
distance
needs to access but not modify fields.translate
modifies the struct fields.mirror_y
returns an entirely new struct, consuming the old one.impl Point { fn new(x: i32, y: i32) -> Point { Point { x: x, y: y } }}fn main() { let p = Point::new(1, 2);}
self
.Point::new()
.Point.new()
.new
.Vec::new()
and Vec::with_capacity(capacity: usize)
are both
constructors for Vec
..=
to specify a range of values. Useful for numerics and char
s....
which is deprecated variant of ..=
._
to bind against any value (like any variable binding) and discard the
binding.let x = 17;match x { 0 ..= 5 => println!("zero through five (inclusive)"), _ => println!("You still lose the game."),}
match
: Referencesref
.let x = 17;match x { ref r => println!("Of type &i32: {}", r),}
ref mut
.mut
.ref
patterns become much less common.let mut x = 17;match x { ref r if x == 5 => println!("{}", r), ref mut r => *r = 5}
let ref
.if-let
Statementsif-let
construct.Resultish
type we defined earlier:enum Resultish { Ok, Warning { code: i32, message: String }, Err(String),}
if-let
StatementsWarning
s and Ok
s.match make_request() { Resultish::Err(_) => println!("Total and utter failure."), _ => println!("ok."),}
if-let
binding:let result = make_request();if let Resultish::Err(s) = result { println!("Total and utter failure: {}", s);} else { println!("ok.");}
while-let
Statementwhile-let
statement, which works like an if-let
,
but iterates until the condition fails to match.while let Resultish::Err(s) = make_request() { println!("Total and utter failure: {}", s);}
@
to create variable bindings for
inner elements.#[derive(Debug)]enum A { None, Some(B) }#[derive(Debug)]enum B { None, Some(i32) }fn foo(x: A) { match x { a @ A::None => println!("a is A::{:?}", a), ref a @ A::Some(B::None) => println!("a is A::{:?}", *a), A::Some(b @ B::Some(_)) => println!("b is B::{:?}", b), }}foo(A::None); // ==> x is A::Nonefoo(A::Some(B::None)); // ==> a is A::Some(None)foo(A::Some(B::Some(5))); // ==> b is B::Some(5)
fn foo(x: &i32) {// ...}
However, we can explicitly provide one instead:
fn bar<'a>(x: &'a i32) {// ...}
'a
, pronounced "tick-a" or "the lifetime a" is a named lifetime
parameter.
<'a>
declares generic parameters, including lifetime parameters.&'a i32
is a reference to an i32
that lives at least as
long as the lifetime 'a
.'a
above, but this isn't always the
case.fn borrow_x_or_y<'a>(x: &'a str, y: &'a str) -> &'a str;
borrow_x_or_y
, all input/output references all have the same lifetime.x
and y
are borrowed (the reference is alive) as long as the returned
reference exists.fn borrow_p<'a, 'b>(p: &'a str, q: &'b str) -> &'a str;
borrow_p
, the output reference has the same lifetime as p
.q
has a separate lifetime with no constrained relationship to p
.p
is borrowed as long as the returned reference exists.R
has a lifetime 'a
, it is guaranteed that it will not
outlive the owner of its underlying data (the value at *R
)R
has a lifetime of 'a
, anything else with the lifetime
'a
is guaranteed to live as long R
.struct
sstruct Pizza(Vec<i32>);struct PizzaSlice<'a> { pizza: &'a Pizza, // <- references in structs must index: u32, // ALWAYS have explicit lifetimes}let p1 = Pizza(vec![1, 2, 3, 4]);{ let s1 = PizzaSlice { pizza: &p1, index: 2 }; // this is okay}let s2;{ let p2 = Pizza(vec![1, 2, 3, 4]); s2 = PizzaSlice { pizza: &p2, index: 2 }; // no good - why?}
struct
s<'b: 'a>
.struct Pizza(Vec<i32>);struct PizzaSlice<'a> { pizza: &'a Pizza, index: u32 }struct PizzaConsumer<'a, 'b: 'a> { // says "b outlives a" slice: PizzaSlice<'a>, // <- currently eating this one pizza: &'b Pizza, // <- so we can get more pizza}fn get_another_slice(c: &mut PizzaConsumer, index: u32) { c.slice = PizzaSlice { pizza: c.pizza, index: index };}let p = Pizza(vec![1, 2, 3, 4]);{ let s = PizzaSlice { pizza: &p, index: 1 }; let mut c = PizzaConsumer { slice: s, pizza: &p }; get_another_slice(&mut c, 2);}
'static
'static
.'static
means that a reference may be kept (and will be valid) for the
lifetime of the entire program.&str
literals have the 'static
lifetime.let s1: &str = "Hello";let s2: &'static str = "World";
struct Foo<'a, 'b> { v: &'a Vec<i32>, s: &'b str,}
impl
BlocksFoo
struct requires lifetime annotations too!'a
and
'b
for the struct Foo
using the lifetimes 'a
and 'b
."impl<'a, 'b> Foo<'a, 'b> { fn new(v: &'a Vec<i32>, s: &'b str) -> Foo<'a, 'b> { Foo { v: v, s: s, } }}
"Rust is a systems programming language that runs blazingly fast, prevents nearly all segfaults, and guarantees thread safety." – prev.rust-lang.org
"Empowering everyone to build reliable and efficient software. " – rust-lang.org
Keyboard shortcuts
↑, ←, Pg Up, k | Go to previous slide |
↓, →, Pg Dn, Space, j | Go to next slide |
Home | Go to first slide |
End | Go to last slide |
b / m / f | Toggle blackout / mirrored / fullscreen mode |
c | Clone slideshow |
p | Toggle presenter mode |
t | Restart the presentation timer |
?, h | Toggle this help |
Esc | Back to slideshow |