1
Rust: Reach Fusthes!
Nichomas Matsakis
Rust: Reach F us th es ! Nich om as Matsakis 1 Q. What is Rust? Q. - - PowerPoint PPT Presentation
Rust: Reach F us th es ! Nich om as Matsakis 1 Q. What is Rust? Q. Why should I care? 2 Q. What is Rust? A. High-level code, low-level performance Q. Why should I care? 3 Decisions GC Control, flexibility
1
Nichomas Matsakis
2
3
4
Double free? Buffer overflow? Dangling pointers? Data races? GC
Control, flexibility
5
Static type system = Eat your spinach!
Photo credit: Sanjoy Ghosh https://www.flickr.com/photos/sanjoy/4016632253/6
Photo credit: Salim Virji https://www.flickr.com/photos/salim/8594532469/Static type system = Eat your spinach!
7
The Rust compiler just saved me from a nasty threading bug. I was working on cage (our open source development tool for Docker apps with lots of microservices), and I decided to parallelize the routine that transformed docker-compose.yml files.
8
Performance
class ::String def blank? /\A[[:space:]]*\z/ == self end end
Ruby: 964K iter/sec
static VALUE rb_str_blank_as(VALUE str) { rb_encoding *enc; char *s, *e; enc = STR_ENC_GET(str); s = RSTRING_PTR(str); if (!s || RSTRING_LEN(str) == 0) return Qtrue; e = RSTRING_END(str); while (s < e) { int n; unsigned int cc = rb_enc_codepoint_len(s, e, &n, enc); switch (cc) { case 9: case 0xa: case 0xb: case 0xc: case 0xd: case 0x20: case 0x85: case 0xa0: case 0x1680: case 0x2000: case 0x2001: case 0x2002: case 0x2003: case 0x2004: case 0x2005: case 0x2006: case 0x2007: case 0x2008: case 0x2009: case 0x200a: case 0x2028: case 0x2029: case 0x202f: case 0x205f: case 0x3000: #if ruby_version_before_2_2() case 0x180e: #endif /* found */ break; default: return Qfalse; } s += n; } return Qtrue; }
Performance Ruby: 964K iter/sec C: 10.5M iter/sec
10x!
https://github.com/SamSaffron/fast_blank
Performance
10
class ::String def blank? /\A[[:space:]]*\z/ == self end end extern “C” fn fast_blank(buf: Buf) -> bool { buf.as_slice().chars().all(|c| c.is_whitespace()) }
Get Rust string slice Get iterator over each character Are all characters whitespace?
Rust: 11M iter/sec
Ruby: 964K iter/sec C: 10.5M iter/sec
11
High-level, zero-cost abstractions
fn is_whitespace(text: &str) -> bool { text.chars() .all(|c| c.is_whitespace()) } fn load_images(paths: &[PathBuf]) -> Vec<Image> { paths.par_iter() .map(|path| Image::load(path)) .collect() }
12
13
Experienced C++ hacker? Prefer Ruby? JavaScript?
Make and maintain the designs you always wanted — but could not justify. Tune up your application and address hot-spots. Lower memory usage. Add threads without fear.
14
I like Rust because it is boring. — CJ Silverio, npm CTO
15
16
“Must be this tall to write multi-threaded code”
David Baron Mozilla Distinguished Engineer
Data races
Sharing Mutation No ordering Data race
17
Actor-based languages (e.g., Erlang, WebWorkers) Functional languages (e.g., Haskell) Sequential programming
Data races
Sharing Mutation No ordering Data race
18
Rust: No sharing and mutation at the same time.
19
Type Ownership Alias? Mutate?
T Owned ✓
~ Ownership and borrowing ~
fn main() { let apple = Apple::new(); eat(apple); }
21
fn eat(apple: Apple) { … }
Take ownership
Give ownership
Owns Owns
eat(apple);
~~~~~~~~~ Error: `apple` has been moved.
fn main() { let apple = Apple::new(); let mut bag = Vec::new(); bag.push(apple); bag.push(Apple::new()); deliver(bag); }
22
fn deliver(bag: Vec<Apple>) { … }
Take ownership
Give ownership. Give ownership.
Owns Owns
23
“Manual” memory management in Rust:
Values owned by creator. Values moved via assignment. When final owner returns, value is freed.
Feels invisible.
~ Ownership and borrowing ~
Type Ownership
T
Alias? Mutate?
Owned ✓ &T Shared reference ✓
fn main() { let apple = Apple::new(); let mut bag = Vec::new(); bag.push(apple); bag.push(Apple::new()); let weight = weigh(&bag); … }
25
fn weigh(bag: &Vec<Apple>) -> u32 { … }
Shared reference to the vector Loan out the bag (Return type)
shared references
~~~~~~~~~
let mut bag = Vec::new(); bag.push(…); let r = &bag; bag.len(); bag.push(…); r.push(…); bag.push(…);
reading `bag` ok while shared cannot mutate while shared
26
Sharing “freezes” data (temporarily)
`bag` mutable here ~~~~~~~~~~~ `bag` borrowed here after last use of `r`, `bag` is mutable again cannot mutate through shared ref
~ Ownership and borrowing ~
Type Ownership
T &T
Alias? Mutate?
Owned Shared reference ✓ ✓ &mut T Mutable reference ✓
cannot access `bag` while borrowed but can mutate through `r`
28
Mutable references: no other access
`bag` mutable here ~~~~~~~~~ `bag` mutably borrowed here after last use of `r`, `bag` is accessible again
let mut bag = Vec::new(); bag.push(…); let r = &mut bag; bag.len(); r.push(…); bag.push(…);
29
30
Observation:
Building parallel abstractions is easy. Misusing those abstractions is also easy.
func foo(…) { m := make(map[string]string) m[“Hello”] = “World” channel <- m m[“Hello”] = “Data Race” }
send data over channel but how to stop sender from using it afterwards? Go Code
31
fn foo(…) { let m = HashMap::new(); m.insert(“Hello”, “World”); channel.send(m); m.insert(“Hello”, “Data Race”); } impl<T> Channel<T> { fn send(&mut self, data: T) { … } }
Take ownership
Error: use of moved value: `book` ~~~~~~~~~~~~~~~~~~~~~~~~~~
~ Concurrency paradigms ~
Paradigm
Message passing
Ownership? Borrowing?
✓ Fork join ✓
33
fn load_images(paths: &[PathBuf]) -> Vec<Image> { paths.iter() .map(|path| { Image::load(path) }) .collect() }
For each path… …load an image… …create and return a vector.
paths = [ “a.jpg”, “b.png”, …, “c.jpg” ]
borrowed from caller
34
fn load_images(paths: &[PathBuf]) -> Vec<Image> { paths.par_iter() .map(|path| { Image::load(path) }) .collect() }
Make it parallel
paths = [ “a.jpg”, “b.png”, …, “c.jpg” ] extern crate rayon;
Third-party library
35
Observation:
Building parallel abstractions is easy. Misusing those abstractions is also easy.
36
fn load_images(paths: &[PathBuf]) -> Vec<Image> { let mut jpgs = 0; paths.par_iter() .map(|path| { if path.ends_with(“.jpg”) { jpgs += 1; } Image::load(path) }) .collect() }
How many jpgs seen so far? …add 1 to the counter. If current file name ends in “jpg”…
+ 1 + 1 1 1 1
fn load_images(paths: &[PathBuf]) -> Vec<Image> { let mut jpgs = 0; paths.par_iter() .map( ) .collect() }
37
borrows mutably borrows mutably
|path| { if path.ends_with(“.jpg”) { jpgs += 1; } Image::load(path) } |path| { if path.ends_with(“.jpg”) { jpgs += 1; } Image::load(path) }
~~~~~~~~~❌ ~~~~~~~~~❌
~ Concurrency paradigms ~
Paradigm
Message passing
Ownership? Borrowing?
✓ Fork join ✓ Lock-free Futures … ✓ ✓ ✓ ✓ Locking ✓ ✓
39
40
Ownership and Borrowing P a r a l l e l i s m File System R e f e r e n c e c
n t
Core Language Libraries
Safe abstractions
unsafe { … }
Ownership/borrowing/traits give tools to enforce safe abstraction boundaries. Trust me.
fn split_at_mut(…) { }
Validates input, etc.
41
42
Stylo (Parallel CSS Rendering — coming in FF57)
Total KLOC Unsafe KLOC Unsafe % Total 146.2 51.7 35% Interesting stuff 71.6 1.4 1.9% FFI Bindings 74.5 50.3 67.4%
43
Photo credit: Don Urban https://www.flickr.com/photos/donpezzano/3044965125/44
usehelix.com
Ruby node.js
neon-bindings/neon PyO3/pyo3 dgrunwald/rust-cpython
CPython
getsentry/milksnake
45
ArrayBuffer Worker
46
ArrayBuffer
fn callback(mut vm: CallContext) { let buffer = vm.arguments(0); let guard: VmGuard = vm.lock(); let data = buffer.borrow_mut(&guard); data .par_iter_mut() .for_each(|i| *i += 1); }
vm guard borrows data
buffer data
47
ArrayBuffer
buffer data
What could go wrong?
What if we invoked JS callback while using `data`? ▶ Can’t: Invoking JS callbacks requires mutable borrow of `vm`! vm guard borrow data
48
https://medium.com/@wireapp/3ff37fc98c3f
49
ruby! { class Console { def log(string: &str) { println!("LOG: {:?}", string); } } }
50
51
Will discuss today! Whitepapers available online.
rust-lang.org/en-US/whitepapers.html
52
Category: Experienced system devs
53
54
55
Photo credit: Salim Virji https://www.flickr.com/photos/salim/8594532469/Safety = Eat your spinach!
56
Gradual adoption works.
57
Category: Full-stack developers
58
"Because our product helps people identify why their apps are slow, it is very important that we
Yehuda Katz, CTO of Tilde.
Performance monitoring software for RoR. Written (initially) in Ruby, but hit performance limitations:
59
Why Rust?
Next, they prototyped the agent in C++. Although the C++ implementation used fewer resources, it also introduced an unacceptable risk of crashes. Katz and his team spent time squeezing every drop
uses of higher-level features of the language that required more resources.
team can write performance critical code.
60
61
62
Focus: Productivity and Ergonomics Culmination of 2017 roadmap efforts across the board: documentation, language, tooling, libraries, etc.
63
RFC Process
64
Rust Teams
Core Team Language Team Library Team Compiler Team Dev Tools Team Cargo Team IDEs and Editor Team Infrastructure Team Release Team Community Team Documentation Team Rustdoc Team Moderation Team
Mostly open-source volunteers. Mozilla employees are a small fraction.
65
Rust Domain Working Groups
Web Services WebAssembly CLI Apps Embedded Devices
66
From http://jvns.ca/blog/2016/09/11/rustconf-keynote/Open and welcoming
67
Want to learn more?
intorust.com rust-lang.org O’Reilly
(Screencasts)
Manning No Starch Armstrong
github.com/ctjhoa/rust-learning
Tons more at: