@pnkfelix
), MozillaNew systems programming language
fast; FFI interface; data layout control
compete (and interface with) with C/C++
Mix in the classic hits of PL
Safety
Memory-safe, data-race free
Language and API docs
All linked from top of http://www.rust-lang.org/
Starting points
The Book: https://doc.rust-lang.org/stable/book/
Standard API: https://doc.rust-lang.org/stable/std/
Playpen: http://play.rust-lang.org/
The below loop demo compiles down to tight code:
// sums all the positive values in `v`
fn sum_pos(v: &Vec<i32>) -> i32 {
let mut sum = 0;
for i in v.iter().filter(|i| **i > 0) {
sum += *i;
}
sum
}
Generated x86_64 machine code for fn sum_pos
:
leaq (%rdi,%rsi,4), %rcx
xorl %eax, %eax
jmp .LBB5_1
.LBB5_3:
addl %edx, %eax
.align 16, 0x90
.LBB5_1:
cmpq %rdi, %rcx
je .LBB5_4
movl (%rdi), %edx
addq $4, %rdi
testl %edx, %edx
jle .LBB5_1
jmp .LBB5_3
.LBB5_4:
retq
Or, if the first was not high-level enough:
fn foo(v: &Vec<i32>) -> i32 {
v.iter().filter(|i| **i > 0).map(|i| *i).sum()
}
(This second loop demo compiles down to the same tight code!)
Example: catches iterator invalidation bugs (aka ConcurrentModificationException
- at compile-time)
fn this_wont_compile(v: &mut Vec<i32>) -> i32 {
let mut sum = 0;
for &i in v.iter() {
sum += i;
if i > 0 { v.push(0); }
// ~~~~~~~~~ invalid! (might realloc
// the backing storage for `v`)
}
sum
}
error: cannot borrow `*v` as mutable because it is also borrowed
as immutable
if i > 0 { v.push(0); }
^
note: previous borrow of `*v` occurs here; the immutable borrow
prevents subsequent moves or mutable borrows of `*v` until
the borrow ends
for &i in v.iter() {
^
See also Fearless Concurrency blog post.
use std::thread;
fn par_max(data: &[u8]) -> u8 {
if data.len() <= 4 { return seq_max(data); }
let len_4 = data.len() / 4; // DATA = [A .., B .., C .., D..]
let (q1, rest) = data.split_at(len_4); // (A.. \ B..C..D..)
let (q2, rest) = rest.split_at(len_4); // (B.. \ C..D..)
let (q3, q4) = rest.split_at(len_4); // (C.. \ D..)
let t1 = thread::scoped(|| seq_max(q1)); // fork A..
let t2 = thread::scoped(|| seq_max(q2)); // fork B..
let t3 = thread::scoped(|| seq_max(q3)); // fork C..
let v4 = seq_max(q4); // compute D..
let (v1, v2, v3) = (t1.join(), t2.join(), t3.join()); // join!
return seq_max(&[v1, v2, v3, v4]);
}
(caveat: above is using unstable thread::scoped
API)
To compete
C/C++ impedes ability to compete in the browser market
Fast experimentation (and deployment)
⇒ Competitive Advantage
browser implementation research platform
written in Rust
parallel paint
parallel layout
parallel css selector matching
What is "soundness"?
Segfault is not okay
But: "clean" shutdown (e.g. on internal error) is okay
Rust has panic!
, and atop it assert!
etc
(A panic unwinds the stack, cleaning up resources)
Rust uses dynamic checks (and resorts to panic!
) where appropriate
Prefers static assurances when feasible
(we are pragmatists)
(explicit resource control)
(brings back reference semantics)
(encode safety constraints between references)
Create (and modify) owned:
#[test]
pub fn create_owned() {
let mut vec = Vec::new(); // + (`vec` created ...
vec.push(2000); // | ... and
vec.push( 400); // | also
vec.push( 60); // | mutated ...
println!("vec: {:?}", vec); // |
let the_sum = sum(vec); // o ... and moved)
// (starting here, access to `vec` is static error)
println!("the_sum: {}", the_sum);
}
At scope end, clean up initialized (+ unmoved) variables
fn sum(v: Vec<i32>) -> i32 { // o
let mut accum = 0; // |
for i in v.iter() { accum += *i; } // |
accum // (p.s. where is `return`?) // |
} // * (`v` destroyed/freed)
Integers, characters, etc use copy semantics
let x: i32 = calculate();
let y = x;
// can still use `x` here
Structured data uses move semantics by default
Copy
"interface"
#[test]
fn demo_owned_vs_copied() {
let moving_value = vec![1, 2, 3];
let copied_value = 17;
let tuple = (moving_value, copied_value);
println!("copied_value: {:?}", copied_value);
println!("moving_value: {:?}", moving_value);
}
error: use of moved value: `moving_value` [E0382]
println!("moving_value: {:?}", moving_value);
^~~~~~~~~~~~
note: `moving_value` moved here because it has type
`collections::vec::Vec<i32>`, which is non-copyable
let tuple = (moving_value, copied_value);
^~~~~~~~~~~~
Imagine programming without reuse
#[test]
fn moves_insufficient() {
let vec = expensive_vector_computation();
let result1 = some_vec_calculation(vec); // <-- `vec` moved here
let result2 = other_calculation(vec); // oops, `vec` is *gone*
combine(result1, result2);
}
error: use of moved value: `vec` [E0382]
let result2 = other_calculation(vec); // oops
^~~
note: `vec` moved here because it has type
`collections::vec::Vec<i32>`, which is non-copyable
let result1 = some_vec_calculation(vec); // <-- `vec` moved here
^~~
Lend references to values
#[test]
fn hooray_for_borrows() {
let vec = expensive_vector_computation();
let result1 = some_vec_calculation(&vec); // <-- lend out `vec`
let result2 = other_calculation(&vec); // <-- lend again, no prob
combine(result1, result2);
} // (`vec` is destroyed/freed aka "dropped" here)
&vec
~~~~
|
a borrow expression
Why are safety violations generally hard to detect?
It is due to aliasing
Borrows reintroduce aliasing
Analogy: RW-lock
Many readers at once, or one writer with exclusive access
Read-only operations do not require exclusive access
Exclusive access requires there are no other readers
Rust uses similar model (but at compile-time) for borrows
T
: base type. Moves, unless bounded by Copy
trait
&T
: shared ref, "read-only" access; copyable
programmer (+ compiler) must assumed aliased
(i.e. "many readers")
&mut T
: "mutable" ref, exclusive access; non-copy
assured unaliased
(i.e. "at most one writer")
fn read(v: &Vec<String>) -> String {
let first: &String = &v[0]; // borrow ref to first elem
println!("v[0]: {}", first);
return first.clone();
}
fn modify(v: &mut Vec<String>, name: &str) {
let freshly_created = format!("Hello {}", name);
v.push(freshly_created);
}
fn consume(v: Vec<String>) -> String {
for s in v { return s; }
panic!("v was empty?!?");
}
fn read(v: &Vec<String>) -> String { ... }
fn modify(v: &mut Vec<String>, name: &str) { ... }
fn consume(v: Vec<String>) -> String { ... }
fn user_input() -> String { format!("World") }
#[test]
fn demo() {
let mut v: Vec<String> = vec![user_input()];
let first = read(&v).clone();
modify(&mut v, &first);
consume(v);
}
#[test]
fn show_some_borrows() {
let v1 = vec![1, 2, 3];
let v2 = vec![4, 5, 6];
let r1 = &v1;
let r2 = &v2;
foo(r1);
foo(r2);
}
fn foo<'a>(v: &'a Vec<i32>) { println!("v[1]: {}", v[1]); }
&v1
and &v2
are borrowing v1
and v2
.
#[test]
fn show_some_lifetimes() {
let v1 = vec![1, 2, 3]; // +
let v2 = vec![4, 5, 6]; // + |
// | |
let r1 = &v1; // + | |
let r2 = &v2; // + | | |
foo(r1); // | | | |
foo(r2); // 'r2 'r1 'v2 'v1
// | | | |
} // * * * *
fn foo<'a>(v: &'a Vec<i32>) { println!("v[1]: {}", v[1]); }
Each borrow selects "appropriate" lifetime 'a
.
fn borrow_checking_prevents_errors() {
let v1 = vec![1, 2, 3]; // +
// |
let r1 = &v1; // + 'v1
// | |
consume(v1); // 'r1 (moved)
foo(r1); // |
} // *
fn foo<'a>(v: &'a Vec<i32>) { println!("v[1]: {}", v[1]); }
fn consume(v: Vec<i32>) { /* `v` *dropped* at scope end */ }
foo(r1)
attempts an indirect read of v1
error: cannot move out of `v1` because it is borrowed
consume(v1);
^~
note: borrow of `v1` occurs here
let r1 = &v1;
^~
fn borrow_checking_may_seem_simple_minded() {
let v1 = vec![1, 2, 3]; // +
// |
let r1 = &v1; // + 'v1
// | |
consume(v1); // 'r1 (moved)
// (no call to read) // |
} // *
fn foo<'a>(v: &'a Vec<i32>) { println!("v[1]: {}", v[1]); }
fn consume(v: Vec<i32>) { }
error: cannot move out of `v1` because it is borrowed
consume(v1);
^~
note: borrow of `v1` occurs here
let r1 = &v1;
^~
(artifact of lexical-scope based implementation)
#[test]
fn copying_can_extend_a_borrows_lifetime_1() {
fn foo<'a>(v: &'a Vec<i32>) { println!("v[1]: {}", v[1]); }
let v1 = vec![1, 2, 3]; // +
let v2 = vec![4, 5, 6]; // | +
let r2 = { // | |
let r1 = &v1; // + | |
// ~~~ <--- A // | | |
foo(r1); // 'r1 | |
&v2 // | 'v1 'v2
}; // * + | |
// (maybe mutate `v1` // | | |
// here someday?) // | | |
// 'r2 | |
foo(r2); // | | |
} // * * *
How long should the borrow &v1
last?
#[test]
fn copying_can_extend_a_borrows_lifetime_2() {
fn foo<'a>(v: &'a Vec<i32>) { println!("v[1]: {}", v[1]); }
let v1 = vec![1, 2, 3]; // +
let v2 = vec![4, 5, 6]; // | +
let r2 = { // | |
let r1 = &v1; // + | |
// ~~~ <--- A // | | |
foo(r1); // 'r1 | |
r1 // <--------- B // | 'v1 'v2
}; // * + | |
// (maybe mutate `v1` // | | |
// here someday?) // | | |
// 'r2 | |
foo(r2); // | | |
} // * * *
How long should the borrow &v1
last now?
(super super useful to be able to share readable data!)
Implications:
#[test]
fn demo_cannot_mutate_imm_borrow() {
let mut v1 = vec![1, 2, 3];
let b = &v1;
let (b1, b2, b3) = (b, b, b);
try_modify(b);
println!("v1: {:?}", v1);
}
fn try_modify(v: &Vec<i32>) {
v.push(4);
}
error: cannot borrow immutable borrowed content `*v` as mutable
v.push(4);
^
Implications:
#[test]
fn demo_cannot_mutate_imm_borrow() {
let mut v1 = vec![1, 2, 3];
let b = &v1;
let (b1, b2, b3) = (b, b, b);
try_modify(b);
println!("v1: {:?}", v1);
}
fn try_modify(v: &Vec<i32>) {
v.push(4);
}
WHAT
A
BUMMER!!!
&mut
borrows#[test]
fn demo_can_mutate_mut_borrow() {
let mut v1 = vec![1, 2, 3];
modify_vec(&mut v1);
println!("v1: {:?}", v1);
}
fn modify_vec(v: &mut Vec<i32>) {
v.push(4);
}
v1: [1, 2, 3, 4]
&mut
mean (crucial)For many (but not all) types, safe mutation requires exclusive access
Any operation requiring exclusive access should either:
take ownership, or,
take an &mut
-reference
fn take_by_value(v: Vec<i32>) { let mut v = v; v.push(4); }
fn take_mut_borrow(b: &mut Vec<i32>) { b.push(10); }
// seemingly similar in power
#[test]
fn demo_exclusive_access_versus_ownership() {
let (mut v1, mut v2) = (vec![1, 2, 3], vec![7, 8, 9]);
take_by_value(v1);
take_mut_borrow(&mut v2);
println!("v1: {:?} v2: {:?}", v1, v2);
}
error: use of moved value: `v1` [E0382]
println!("v1: {:?} v2: {:?}", v1, v2);
^~
note: `v1` moved here
take_by_value(v1);
^~
ownership ⇒ power + responsibility (for dropping)
&mut
⇒ power without responsibility; (can only swap)
&mut
safety enforcement&mut
borrowfn take2<'a>(v1: &'a mut Vec<i32>, v2: &'a Vec<i32>) { }
#[test]
fn demo_cannot_mut_borrow_multiple_times() {
let mut v1 = vec![1, 2, 3];
let mut v2 = vec![1, 2, 3];
take2(&mut v1, &mut v2); // <-- this is okay
take2(&mut v1, &mut v1);
}
error: cannot borrow `v1` as mutable more than once at a time
take2(&mut v1, &mut v1);
^~
note: previous borrow of `v1` occurs here; the mutable borrow
prevents subsequent moves, borrows, or modification of
`v1` until the borrow ends
take2(&mut v1, &mut v1);
^~
&mut
-borrowed datafn take2<'a>(v1: &'a mut Vec<i32>, v2: &'a Vec<i32>) { }
#[test]
fn demo_cannot_alias_mut_borrowed_data() {
let mut v1 = vec![1, 2, 3];
let mut v2 = vec![1, 2, 3];
take2(&mut v1, &v2); // <-- this is okay
take2(&mut v1, &v1);
}
error: cannot borrow `v1` as immutable because it is also borrowed
as mutable
take2(&mut v1, &v1);
^~
note: previous borrow of `v1` occurs here; the mutable borrow
prevents subsequent moves, borrows, or modification of `v1`
until the borrow ends
take2(&mut v1, &v1);
^~
&mut T
is non-copyfn take2<'a>(v1: &'a mut Vec<i32>, v2: &'a Vec<i32>) { }
#[test]
fn demo_cannot_copy_mut_borrows() {
let mut v1 = vec![1, 2, 3];
let b = &mut v1;
let c = b;
take2(b, c);
}
error: use of moved value: `*b` [E0382]
take2(b, c);
^
note: `b` moved here because it has type
`&mut collections::vec::Vec<i32>`, which is moved by default
let c = b;
^
(ensures exclusive access)
a.method()
?struct Point { x: i32, y: i32 }
impl Point {
fn distance_from_origin(&self) -> i32 {
let Point { x, y } = *self;
let sum = (x*x + y*y) as f64;
sum.sqrt() as i32
}
}
#[test]
fn demo_dist() {
let p = Point { x: 3, y: 4 };
assert_eq!(5, p.distance_from_origin());
}
self
: consumes receiver&self
: accesses receiver&mut self
: mutates receiverAccesses receiver
enum Option<T> { None, Some(T) } // algebraic data! generics!
impl<T> Option<T> {
// &Option<T> -> bool
fn is_some(&self) -> bool {
match *self {
Some(_) => true,
None => false
}
}
fn is_none(&self) -> bool {
!self.is_some()
}
}
"Mutates" receiver
enum Option<T> { None, Some(T) }
impl<T> Option<T> {
// &mut Option<T> -> Option<T>
fn take(&mut self) -> Option<T> {
let mut src = None;
swap(self, &mut src);
src
}
}
"Consumes" Takes ownership of receiver
impl<T> Option<T> {
// Option<T> -> T
fn unwrap_or(self, default: T) -> T {
match self {
Some(x) => x,
None => default
}
} // one of `self` or `default` dropped at end
}
impl<T> Option<T> {
fn is_some(&self) -> bool { ... }
fn is_none(&self) -> bool { ... }
fn take(&mut self) -> Option<T> { ... }
fn unwrap_or(self, default: T) -> T { ... }
}
Some magic: method invocations automatically do borrows and derefs as necessary on the receiver ("auto-ref").
fn demo_subroutine(name: String) {
let mut hi = Some(format!("Hello {}", &name));
assert!(hi.is_some()); // this is an `&hi`
let grabbed = hi.take(); // this is an `&mut hi`
assert!(hi.is_none()); // this is an `&hi`
// and this consumes `grabbed`
assert!(&grabbed.unwrap_or(name)[0..5] == "Hello");
}
Box<T>
: unique reference to T
on (malloc
/free
-style) heap
Rc<T>
: shared ownership, thread-local
Arc<T>
: shared ownership, safe across threads
All of above deref to &T
Deref
works here too#[test]
fn demo_rc_of_point() {
use std::rc::Rc;
let p1 = Rc::new(Point { x: 3, y: 4 });
let p2 = p1.clone();
assert_eq!(5, p1.distance_from_origin());
assert_eq!(5, p2.distance_from_origin());
// p1.x = 6; // (STATIC ERROR; cannot assign Rc contents)
}
Box<T>
, Rc<T>
, Arc<T>
all deref to &T
But we also have things like this:
pub enum Cow<'a, B> where B: ToOwned {
Borrowed(&'a B),
Owned(<B as ToOwned>::Owned)
}
which derefs to &B
, even if it is in the Owned
variant.
(Useful for APIs that want to delay the decision of whether to return an owned value or a borrowed reference.)
We saw this kind of thing before:
#[test]
fn explicit_lifetime_binding_1() {
fn print<'a>(ints: &'a Vec<i32>) {
println!("v_1: {}", ints[1]);
}
let v1 = vec![1, 2, 3];
print(&v1)
}
You can bind distinct lifetimes:
#[test]
fn explicit_lifetime_binding_2() {
fn print<'a, 'b>(ptrs: &'a Vec<&'b i32>) {
println!("v_1: {}", ptrs[1]);
}
let one = 1;
let two = 2;
let three = 3;
let four = 4;
let v1 = vec![&one, &two, &three];
print(&v1)
}
Encode constraints by reusing same lifetime:
#[test]
fn explicit_lifetime_binding_3() {
fn print<'a, 'b>(ptrs: &'a mut Vec<&'b i32>, ptr: &'b i32) {
println!("v_1: {}", ptrs[1]);
ptrs.push(ptr);
}
let one = 1;
let two = 2;
let three = 3;
let four = 4;
let mut v1 = vec![&one, &two, &three];
print(&mut v1, &four);
}
Encode constraints by reusing same lifetime:
#[test]
fn explicit_lifetime_binding_4() {
fn print<'a, 'b>(ptrs: &'a mut Vec<&'b i32>, ptr: &'b i32) {
println!("v_1: {}", ptrs[1]);//~~~ ~~~
ptrs.push(ptr); // | |
} // this must match that,
let one = 1; // otherwise push is bogus
let two = 2;
let three = 3;
let four = 4;
let mut v1 = vec![&one, &two, &three];
print(&mut v1, &four);
}
Compiler catches missing necessary constraints:
#[test]
fn explicit_lifetime_binding_5() {
fn print<'a, 'b, 'c>(ptrs: &'a mut Vec<&'b i32>, ptr: &'c i32) {
println!("v_1: {}", ptrs[1]); // ~~~ ~~~
ptrs.push(ptr); // | |
} // this must match that,
let one = 1; // otherwise push is bogus
}
error: cannot infer an appropriate lifetime for automatic coercion
due to conflicting requirements
ptrs.push(ptr);
^~~
help: consider using an explicit lifetime parameter as shown:
fn print<'a, 'b>(ptrs: &'a mut Vec<&'b i32>, ptr: &'b i32)
fn first_n_last<'a>(ints: &'a Vec<i32>) -> (&'a i32, &'a i32) {
// ~~~~~~~ ~~~~~~~
(&ints[0], &ints[ints.len() - 1])
}
#[test]
fn demo_borrowed_return_values() {
let v = vec![1, 2, 3, 4];
let (first, fourth) = first_n_last(&v);
assert_eq!(*first, 1);
assert_eq!(*fourth, 4);
}
(compiler ensures borrow &v
lasts long enough to satisfy reads of first
and fourth
)
fn first_n_last<'a>(ints: Vec<i32>) -> (&'a i32, &'a i32) {
// ~~~~~~~~ (hint)
(&ints[0], &ints[ints.len() - 1])
}
Why doesn't this work?
error: `ints` does not live long enough
(&ints[0], &ints[ints.len() - 1])
^~~~
note: reference must be valid for the lifetime 'a ...
note: ...but borrowed value is only valid for the scope of
note: parameters for function
caller chooses 'a
; fn
body must work for any such choice
(Parameters dropped at scope end; won't live long enough)
'a
, 'b
, ... are ugly#[test]
fn lifetime_elision_1() {
fn print1<'a>(ints: &'a Vec<i32>) {
println!("v_1: {}", ints[1]);
}
fn print2<'a, 'b>(ptrs: &'a Vec<&'b i32>) {
println!("v_1: {}", ptrs[1]);
}
fn print3<'a, 'b>(ptrs: &'a mut Vec<&'b i32>, ptr: &'b i32) {
println!("v_1: {}", ptrs[1]);
ptrs.push(ptr);
}
}
#[test]
fn lifetime_elision_2() {
fn print1 (ints: & Vec<i32>) {
println!("v_1: {}", ints[1]);
}
fn print2 (ptrs: & Vec<& i32>) {
println!("v_1: {}", ptrs[1]);
}
fn print3< 'b>(ptrs: & mut Vec<&'b i32>, ptr: &'b i32) {
println!("v_1: {}", ptrs[1]);
ptrs.push(ptr);
}
}
#[test]
fn lifetime_elision_3() {
fn print1(ints: &Vec<i32>) {
println!("v_1: {}", ints[1]);
}
fn print2(ptrs: &Vec<&i32>) {
println!("v_1: {}", ptrs[1]);
}
fn print3<'b>(ptrs: &mut Vec<&'b i32>, ptr: &'b i32) {
println!("v_1: {}", ptrs[1]);
ptrs.push(ptr);
}
}
#[test]
fn generic_items_1() {
fn push_twice<'b>(ptrs: &mut Vec<&'b i32>, ptr: &'b i32) {
ptrs.push(ptr);
ptrs.push(ptr);
}
let (one, two, three, four) = (1, 2, 3, 4);
let mut v = vec![&one, &two, &three];
push_twice(&mut v, &four);
}
This obviously generalizes beyond i32
!
#[test]
fn generic_items_2() {
fn push_twice<'b, T>(ptrs: &mut Vec<&'b T>, ptr: &'b T) {
ptrs.push(ptr);
ptrs.push(ptr);
}
let (one, two, three, four) = (1, 2, 3, 4);
let mut v = vec![&one, &two, &three];
push_twice(&mut v, &four);
}
This is going so smoothly; lets try printing v_1
again!
#[test]
fn generic_items_3() {
fn push_twice<'b, T>(ptrs: &mut Vec<&'b T>, ptr: &'b T) {
println!("v_1: {}", ptrs[1]);
ptrs.push(ptr);
ptrs.push(ptr);
}
let (one, two, three, four) = (1, 2, 3, 4);
let mut v = vec![&one, &two, &three];
push_twice(&mut v, &four);
}
error: trait `core::fmt::Display` not implemented for the type `T`
println!("v_1: {}", ptrs[1]);
^~~~~~~
(Reminder: Rust is not C++)
trait Dimensioned {
fn height(&self) -> u32;
fn width(&self) -> u32;
}
fn stacked_height<S>(v: &[S]) -> u32 where S: Dimensioned {
let mut accum = 0;
for s in v { accum += s.height() }
accum
}
struct Rect { w: u32, h: u32 }
struct Circle { r: u32 }
impl Dimensioned for Rect {
fn height(&self) -> u32 { self.h }
fn width(&self) -> u32 { self.w }
}
impl Dimensioned for Circle {
fn height(&self) -> u32 { self.r * 2 }
fn width(&self) -> u32 { self.r * 2 }
}
impl Rect {
fn square(l: u32) -> Rect { Rect { w: l, h: l } }
}
impl Circle {
fn with_radius(r: u32) -> Circle { Circle { r: r } }
}
#[test]
fn trait_bounded_polymorphism() {
let squares = [ Rect::square(1), Rect::square(2) ];
let circles = [ Circle::with_radius(1), Circle::with_radius(2)];
assert_eq!(stacked_height(&squares), 3);
assert_eq!(stacked_height(&circles), 6);
}
#[test]
fn parametric_fail() {
let shapes = [Rect::square(1), Circle::with_radius(2)];
assert_eq!(stacked_height(&shapes), 5);
}
error: mismatched types:
expected `Rect`,
found `Circle`
let shapes = [Rect::square(1), Circle::with_radius(2)];
^~~~~~~~~~~~~~~~~~~~~~
T
in Vec<T>
is whystruct Rect { w: u32, h: u32 }
struct Circle { r: u32 }
fn parametric_fail() {
let shapes = [Rect::square(1), Circle::with_radius(2)];
// ~~~~~~ ~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~
// | | |
// | This is 8 bytes This is 4-bytes
// |
// There's no uniform array
// type to hold both in-line.
}
fn stacked_obj_refs(v: &[&Dimensioned]) -> u32 {
let mut accum = 0;
for s in v { accum += s.height() }
accum
}
#[test]
fn demo_objs_1() {
let r = Rect::square(1);
let c = Circle::with_radius(2);
let shapes: [&Dimensioned; 2] = [&r, &c];
assert_eq!(stacked_obj_refs(&shapes), 5);
}
fn stacked_obj_boxes(v: &[Box<Dimensioned>]) -> u32 {
let mut accum = 0;
for s in v { accum += s.height() }
accum
}
#[test]
fn demo_objs_2() {
let shapes: [Box<Dimensioned>; 2] =
[Box::new(Rect::square(1)), Box::new(Circle::with_radius(2))];
assert_eq!(stacked_obj_boxes(&shapes), 5);
}
Can pass functions around as first class entities
Functions can close over externally defined state
Reminder from Javascript:
closures.js
function add3(x) { return x + 3; }
// take function as parameter:
function do_twice(f, y) { return f(f(y)); }
// return function that references outer parameter `z`
function make_adder(z) {
return function(w) { return w + z; };
}
var add4 = make_adder(4);
var ten = do_twice(add4, 2);
In (classic) Javascript, closure syntax is:
function (args, ...) { body; ... }
where body
can refer to things from outside.
In Rust, the analogous closure expression syntax is:
|args, ...| { body; ... }
with a few extra details:
opt. move
(forces capture-by-move)
opt. arg. and return types (inferred when omitted)
#[test]
fn demo_closure() {
fn add3(x: i32) -> i32 { x + 3 } // <- fn, *not* a closure
fn do_twice1<F:Fn(i32) -> i32>(f: F, x: i32) -> i32 { f(f(x)) }
// ~~~~~~~~~~~~~~ closure type
fn do_twice2(f: &Fn(i32) -> i32, x: i32) -> i32 { f(f(x)) }
fn make_adder(y: i32) -> Box<Fn(i32) -> i32> {
Box::new(move |x| { x + y })
// ~~~~~~~~~~~~~~~~~~ closure expression
}
let add4 = make_adder(4);
let six = do_twice1(&add3, 0); let ten = do_twice1(&*add4, 2);
assert_eq!((six, ten), (6, 10));
let six = do_twice2(&add3, 0); let ten = do_twice2(&*add4, 2);
assert_eq!((six, ten), (6, 10));
}
&mut
?Cell
and RefCell
structsBoth types have mutator methods that take &self
&mut self
Cell<T>
: has get
and set
methods, but only accepts T:Copy
RefCell<T>
handles all types T
, but dynamically enforces the rules.
borrow
method returns read-only Ref
(many-readers), and panic!
's on outstanding mut-borrow
borrow_mut
method returns read/write RefMut
, and panic!
's on any outstanding borrow.
Cell
and RefCell
typesCell
working:#[test]
fn demo_cell() {
use std::cell::Cell;
let c = Cell::new(3);
let p1: &Cell<i32> = &c;
let p2: &Cell<i32> = &c;
assert_eq!(p2.get(), 3);
p1.set(4);
assert_eq!(p2.get(), 4);
}
Cell
cannot hold non-Copy
data#[test]
fn demo_cell_vec_no_work() {
use std::cell::Cell;
let c = Cell::new(vec![1,2,3]);
let p1: &Cell<Vec<i32>> = &c;
let p2: &Cell<Vec<i32>> = &c;
assert_eq!(p2.get(), [1,2,3]);
p1.set(vec![4,5,6]);
assert_eq!(p2.get(), [4,5,6]);
}
error: the trait `core::marker::Copy` is not implemented
for the type `collections::vec::Vec<_>` [E0277]
let c = Cell::new(vec![1,2,3]);
^~~~~~~~~
RefCell
handles all data#[test]
fn demo_refcell_vec() {
use std::cell::RefCell;
let c = RefCell::new(vec![1,2,3]);
let p1: &RefCell<Vec<i32>> = &c;
let p2: &RefCell<Vec<i32>> = &c;
assert_eq!(*p2.borrow(), [1,2,3]);
p1.borrow_mut().push(4);
assert_eq!(*p2.borrow(), [1,2,3,4]);
}
RefCell
dynamically errors if misused#[test]
fn demo_refcell_vec_no_work() {
use std::cell::RefCell;
let c = RefCell::new(vec![1,2,3]);
let p1: &RefCell<Vec<i32>> = &c;
let p2: &RefCell<Vec<i32>> = &c;
let b2 = p2.borrow();
assert_eq!(*b2, [1,2,3]);
p1.borrow_mut().push(4);
assert_eq!(*b2, [1,2,3,4]);
}
---- a01_start::demo_refcell_vec_no_work stdout ----
thread 'a01_start::demo_refcell_vec_no_work' panicked
at 'RefCell<T> already borrowed'
(This is not unsound!)
Drop
traitIf a data-type is represents some resource with associated cleanup actions, then it should implement Drop
struct Yell { name: &'static str }
impl Drop for Yell {
fn drop(&mut self) {
println!("My name is {}; ", self.name);
}
}
fn main() {
let bob = Yell { name: "Bob" };
let carol = Yell {
name: {
let carols_dad = Yell { name: "David" };
"Carol"
} // end of scope: carols_dad is dropped
};
let ed = Yell { name: "Ed" };
let frank = Yell { name: panic!("What is Frank's name?") };
println!("We never get to Gregor");
let gregor = Yell { name: "Gregor" };
}
prints:
My name is David;
thread '<main>' panicked at 'What is Frank's name?', <anon>:16
My name is Ed;
My name is Carol;
My name is Bob;
Rc
implementationstruct RcBox<T> { strong: Cell<usize>, value: T }
pub struct Rc<T> { _ptr: NonZero<*mut RcBox<T>> }
impl<T: ?Sized> Clone for Rc<T> {
fn clone(&self) -> Rc<T> {
self.inc_strong();
Rc { _ptr: self._ptr }
}
}
impl<T> Drop for Rc<T> {
fn drop(&mut self) {
unsafe {
let ptr = *self._ptr;
self.decrement_count();
if self.current_count() == 0 {
drop_in_place(&mut (*ptr).value);
deallocate(ptr);
}
}
}
}
Rust's killer feature:
built atop same foundation as memory safety
thread::spawn
pub fn main() {
use std::thread;
let al = "long lost pal";
thread::spawn(move || {
println!("i can be your {}", al);
});
println!("why am i soft in the middle");
// Note: might exit before spawned thread gets chance to print
}
#[test] fn demo_channel() {
fn fib(x: i64) -> (i64, i64) { // returns `(x, fib(x))`
if x <= 1 { (x,1) } else { (x, fib(x-1).1 + fib(x-2).1) }
}
use std::thread;
use std::sync::mpsc::channel;
let (tx, rx) = channel(); // tx: "transmit", rx: "receive"
let al = "al";
thread::spawn(move || {
tx.send(fib(10)).unwrap();
println!("you can call me {}", al);
});
let f_15 = fib(15).1;
println!("why am i short of attention");
let f_10 = rx.recv().unwrap().1; // (this blocks to await data)
assert_eq!((f_10, f_15), (89, 987));
}
#[test] fn demo_catch_direct() {
fn fib(x: i64) -> (i64, i64) { // returns `(x, fib(x))`
if x <= 1 { (x,1) } else { (x, fib(x-1).1 + fib(x-2).1) }
}
use std::thread;
let al = "al";
let mut f_10_recv = (0, 0);
thread::spawn(move || {
f_10_recv = fib(10);
println!("you can call me {}", al);
});
let f_15 = fib(15).1;
while f_10_recv.0 == 0 { } // <-- many alarm bells
let f_10 = f_10_recv.1;
println!("why am i short of attention");
assert_eq!((f_10, f_15), (89, 987));
}
#[test] fn demo_catch_mutref() {
fn fib(x: i64) -> (i64, i64) { // returns `(x, fib(x))`
if x <= 1 { (x,1) } else { (x, fib(x-1).1 + fib(x-2).1) }
}
use std::thread;
let al = "al";
let mut f_10_recv = (0, 0);
let ptr_recv = &mut f_10_recv; // <-- Okay, say what we meant
thread::spawn(move || {
*ptr_recv = fib(10);
println!("you can call me {}", al);
});
let f_15 = fib(15).1;
while f_10_recv.0 == 0 { } // <-- many alarm bells
let f_10 = f_10_recv.1;
println!("why am i short of attention");
assert_eq!((f_10, f_15), (89, 987));
}
spawn
can't share ref to stack-locale.g., Apple's Grand Central Dispatch
Rust wrapper developed by 3rd party contributor
Cargo.toml
[dependencies]
dispatch = "0.0.1"
fn demo_gcd() {
use dispatch::{Queue, QueuePriority};
let queue = Queue::global(QueuePriority::Default);
let mut nums = vec![1, 2];
queue.apply(&mut nums, |x| *x += 1);
assert!(nums == [2, 3]);
let nums = queue.map(nums, |x| x.to_string());
assert!(nums[0] == "2");
}
Implementation almost certainly relies on unsafe
code
"buyer beware" (i.e. audit, in some manner)
dispatch
misuse, caught!fn demo_gcd2() {
use dispatch::{Queue, QueuePriority};
let queue = Queue::global(QueuePriority::Default);
let mut indices = vec![1, 0];
let mut nums = vec![1, 2];
queue.apply(&mut indices, |i| nums[*i] += 1);
assert!(nums == [2, 3]);
let nums = queue.map(nums, |x| x.to_string());
assert!(nums[0] == "2");
}
(Type system catches above attempt to close over &mut
-references in a closure that is not allowed carry such.)
thread::spawn
child and parent proceed independently
parent can share stack-local state with child thread(s)
thread::spawn
: child and parent cannot share stack state
fork-join: parent must wait for children before returning
We can encode both constraints in Rust
thread::scoped
fn seq_max(partial_data: &[u8]) -> u8 {
*partial_data.iter().max().unwrap()
}
fn par_max(data: &[u8]) -> u8 {
if data.len() <= 4 { return seq_max(data); }
let len_4 = data.len() / 4; // DATA = [A..B..C..D..]
let (q1, rest) = data.split_at(len_4); // (A.. \ B..C..D..)
let (q2, rest) = rest.split_at(len_4); // (B.. \ C..D..)
let (q3, q4) = rest.split_at(len_4); // (C.. \ D..)
let t1 = ::std::thread::scoped(|| seq_max(q1)); // fork A..
let t2 = ::std::thread::scoped(|| seq_max(q2)); // fork B..
let t3 = ::std::thread::scoped(|| seq_max(q3)); // fork C..
let v4 = seq_max(q4); // compute D..
let (v1, v2, v3) = (t1.join(), t2.join(), t3.join()); // join!
return seq_max(&[v1, v2, v3, v4]);
}
thread::scoped
shows a new trickthread::spawn
disallowed passing refs to stack-local data
Allowing that is the whole point of thread::scoped
thread::scoped
API is unstable, and undergoing revision due to subtle soundness issue)par_max
1extern crate test; use std::iter;
const LIL: usize = 20 * 1024;
const BIG: usize = LIL * 1024;
fn make_data(count: usize) -> Vec<u8> {
let mut data: Vec<u8> = iter::repeat(10).take(count).collect();
data.push(200); data.push(3); return data;
}
#[bench] fn bench_big_seq(b: &mut test::Bencher) {
let data = make_data(BIG);
b.iter(|| assert_eq!(seq_max(&data), 200));
}
#[bench] fn bench_big_par(b: &mut test::Bencher) {
let data = make_data(BIG);
b.iter(|| assert_eq!(par_max(&data), 200));
}
bench_big_par ... bench: 3,763,711 ns/iter (+/- 1,140,321)
bench_big_seq ... bench: 21,633,799 ns/iter (+/- 2,522,262)
par_max
2const LIL: usize = 20 * 1024;
const BIG: usize = LIL * 1024;
bench_big_par ... bench: 3,763,711 ns/iter (+/- 1,140,321)
bench_big_seq ... bench: 21,633,799 ns/iter (+/- 2,522,262)
#[bench] fn bench_lil_seq(b: &mut test::Bencher) {
let data = make_data(LIL);
b.iter(|| assert_eq!(seq_max(&data), 200));
}
#[bench] fn bench_lil_par(b: &mut test::Bencher) {
let data = make_data(LIL);
b.iter(|| assert_eq!(par_max(&data), 200));
}
bench_lil_par ... bench: 59,274 ns/iter (+/- 7,756)
bench_lil_seq ... bench: 15,432 ns/iter (+/- 1,961)
(fn par_max
could tune threshold for seq. path)
Send
, Sync
If S: Send
, then passing (e.g. moving) a S
to another thread is safe.
If T: Sync
, then copying a &T
to another thread is safe.
(For Rust, "safe" includes "no data races exposed.")
Send
and Sync
traitsIf S: Send
, can transfer S
across thread boundaries
OTOH: A type T
implementing Sync
or might might not be sendable...
...but &T
is always Send
for a T: Sync
(!)
(This is what makes Sync
interesting)
Rust compiler automatically marks types as Send
or Sync
, based on a recursive analysis of their structure
Rust compiler automatically marks types as Send
or Sync
, based on a recursive analysis of their structure
e.g.:
i32
is Send
.
Vec<T>
: Send
iff T: Send
Box<T>
(owned): Send
iff T: Send
, Sync
iff T: Sync
Send
rule? because can move T
out of a (consumed) box.Rc<T>
(shared): neither Send
nor Sync
(for any T
)
Arc<T>
(shared): always requires T: Send + Sync
(remember that Rc<T>
and Arc<T>
do not allow &mut
access to T
itself)
Send
/Sync
demo: Box
/Rc
/Arc
fn is_sync<T:Sync>(t: T) {} // Felix: too lazy to construct
fn is_send<T:Send>(t: T) {} // appropriate channels
#[test]
fn demo_send_sync_vals_refs_box_rc_arc() {
use std::rc::Rc;
use std::sync::Arc;
is_sync(3);
is_sync(vec![1,2,3]);
is_send(3);
is_send(&vec![1,2,3]);
is_sync(&3);
is_sync(&vec![1,2,3]);
is_send(&3);
is_send(&vec![1,2,3]);
is_sync(Box::new(3));
is_sync(Box::new(vec![1,2,3]));
is_send(Box::new(3));
is_send(Box::new(vec![1,2,3]));
}
Send
/Sync
demo: Box
Rules#[test]
fn demo_send_sync_box_rules() {
fn take_send<T:Send+Clone>(t: T) {
// is_sync(Box::new(t.clone())); // (STATIC ERROR)
is_send(Box::new(t.clone()));
// is_sync(&Box::new(t.clone())); // (STATIC ERROR)
// is_send(&Box::new(t.clone())); // (STATIC ERROR)
}
fn take_sync<T:Sync+Clone>(t: T) {
is_sync(Box::new(t.clone()));
// is_send(Box::new(t.clone())); // (STATIC ERROR)
is_sync(&Box::new(t.clone()));
is_send(&Box::new(t.clone()));
}
fn take_send_sync<T:Send+Sync+Clone>(t: T) {
is_sync(Box::new(t.clone()));
is_send(Box::new(t.clone()));
is_sync(&Box::new(t.clone()));
is_send(&Box::new(t.clone()));
}
}
Send
/Sync
demo: Rc
values#[test]
fn demo_send_sync_rc() {
use std::rc::Rc;
// is_sync(Rc::new(3)); // (STATIC ERROR)
// is_sync(Rc::new(vec![1,2,3])); // (STATIC ERROR)
// is_send(Rc::new(3)); // (STATIC ERROR)
// is_send(Rc::new(vec![1,2,3])); // (STATIC ERROR)
// is_sync(vec![Rc::new(1)]); // (STATIC ERROR)
// is_send(vec![Rc::new(1)]); // (STATIC ERROR)
// is_sync(&vec![Rc::new(1)]); // (STATIC ERROR)
// is_send(&vec![Rc::new(1)]); // (STATIC ERROR)
// is_sync(Box::new(Rc::new(1))); // (STATIC ERROR)
// is_send(Box::new(Rc::new(1))); // (STATIC ERROR)
// is_sync(&Box::new(Rc::new(1))); // (STATIC ERROR)
// is_send(&Box::new(Rc::new(1))); // (STATIC ERROR)
}
Send
/Sync
demo: Arc
values#[test]
fn demo_send_sync_arc() {
use std::sync::Arc;
is_sync(vec![Arc::new(1)]);
is_send(vec![Arc::new(1)]);
is_sync(&vec![Arc::new(1)]);
is_send(&vec![Arc::new(1)]);
is_sync(Arc::new(3));
is_sync(Arc::new(vec![1,2,3]));
is_send(Arc::new(3));
is_send(Arc::new(vec![1,2,3]));
}
Send
/Sync
demo: Arc
rules#[test]
fn demo_send_sync_arc_rules() {
use std::sync::Arc;
fn take_send<T:Send+Clone>(t: T) {
// is_sync(Arc::new(t.clone())); // (STATIC ERROR)
// is_send(Arc::new(t.clone())); // (STATIC ERROR)
// is_sync(&Arc::new(t.clone())); // (STATIC ERROR)
// is_send(&Arc::new(t.clone())); // (STATIC ERROR)
}
fn take_sync<T:Sync+Clone>(t: T) {
// is_sync(Arc::new(t.clone())); // (STATIC ERROR)
// is_send(Arc::new(t.clone())); // (STATIC ERROR)
// is_sync(&Arc::new(t.clone())); // (STATIC ERROR)
// is_send(&Arc::new(t.clone())); // (STATIC ERROR)
}
fn take_send_sync<T:Send+Sync+Clone>(t: T) {
is_sync(Arc::new(t.clone()));
is_send(Arc::new(t.clone()));
is_sync(&Arc::new(t.clone()));
is_send(&Arc::new(t.clone()));
}
}
Send
/Sync
demo: Cell
and RefCell
values#[test]
fn demo_send_sync_cell_refcell() {
use std::cell::Cell;
use std::cell::RefCell;
// is_sync(Cell::new(3)); // (STATIC ERROR)
// Cell::new(vec![1,2,3]);
is_send(Cell::new(3));
// is_sync(&Cell::new(3)); // (STATIC ERROR)
// is_send(&Cell::new(3)); // (STATIC ERROR)
// is_sync(RefCell::new(3)); // (STATIC ERROR)
// is_sync(RefCell::new(vec![1,2,3])); // (STATIC ERROR)
is_send(RefCell::new(3));
is_send(RefCell::new(vec![1,2,3]));
}
Access to (many!) safe concurrency primitives
Access arbitrary native services
Ownership and Borrowing are deep parts of language
rustc
catches bugs!