rust reach f us th es
play

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. Rust: Reach F us th es ! Nich om as Matsakis 1

  2. Q. What is Rust? Q. Why should I care? 2

  3. Q. What is Rust? A. High-level code, low-level performance Q. Why should I care? 3

  4. Decisions… GC 😁 😖 😏 Control, flexibility 😲 😁 😏 Double free? 😲 😁 😏 Dangling pointers? 😲 😁 😏 Buffer overflow? 😲 😲 😏 Data races? 4

  5. Static type system = Eat your spinach! Photo credit: Sanjoy Ghosh 5 https://www.flickr.com/photos/sanjoy/4016632253/

  6. Static type system = Eat your spinach! Photo credit: Salim Virji 6 https://www.flickr.com/photos/salim/8594532469/

  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. 7

  8. Performance Ruby: class ::String 964K iter/sec def blank? /\A[[:space:]]*\z/ == self end end 8

  9. static VALUE case 0x2005: Performance rb_str_blank_as(VALUE str) case 0x2006: { case 0x2007: rb_encoding *enc; case 0x2008: char *s, *e; case 0x2009: case 0x200a: enc = STR_ENC_GET(str); case 0x2028: s = RSTRING_PTR(str); case 0x2029: Ruby: if (!s || RSTRING_LEN(str) == 0) return Qtrue; case 0x202f: case 0x205f: 964K iter/sec e = RSTRING_END(str); case 0x3000: while (s < e) { #if ruby_version_before_2_2() int n; case 0x180e: unsigned int cc = rb_enc_codepoint_len(s, e, &n, enc); #endif 10x! /* found */ switch (cc) { break ; case 9: default : case 0xa: return Qfalse; case 0xb: } C: case 0xc: s += n; case 0xd: } 10.5M iter/sec case 0x20: return Qtrue; case 0x85: } case 0xa0: case 0x1680: case 0x2000: case 0x2001: case 0x2002: case 0x2003: https://github.com/SamSaffron/fast_blank case 0x2004:

  10. Performance Ruby: class ::String 964K iter/sec def blank? /\A[[:space:]]*\z/ == self C: end 10.5M iter/sec end extern “C” fn fast_blank(buf: Buf) -> bool { Rust: buf.as_slice().chars().all(|c| c.is_whitespace()) 11M iter/sec } Get Rust Get iterator over Are all characters string slice each character whitespace? 10

  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() } 11

  12. Q. What is Rust? A. High-level code, low-level performance Q. Why should I care? A. You can do more! 12

  13. Experienced C++ hacker? Make and maintain the designs you always wanted — but could not justify. Prefer Ruby? JavaScript? Tune up your application and address hot-spots. Lower memory usage. Add threads without fear. 13

  14. I like Rust because it is boring . — CJ Silverio, npm CTO 14

  15. Design of Rust Rust in Production Rust Community 15

  16. “Must be this tall to write multi-threaded code” David Baron 
 Mozilla Distinguished Engineer 16

  17. Data races Actor-based languages Sharing (e.g., Erlang, WebWorkers) Functional languages Mutation (e.g., Haskell) No ordering Sequential programming Data race 17

  18. Data races Sharing Rust: No sharing and 
 mutation at the same time. Mutation No ordering Data race 18

  19. Ownership and Borrowing Photo Credit: Nathan Kam https://www.youtube.com/watch?v=Tnssn9KcWLg 19

  20. ~ Ownership and borrowing ~ Type Ownership Alias? Mutate? ✓ T Owned

  21. fn main() { fn eat(apple: Apple) { let apple = Apple::new(); … eat(apple); } Give ownership eat(apple); of the apple. ~~~~~~~~~ Take ownership } Error: `apple` has of the apple been moved. Owns Owns 21

  22. fn main() { fn deliver(bag: Vec<Apple>) { let apple = Apple::new(); … let mut bag = Vec::new(); } bag.push(apple); Give ownership. Take ownership bag.push(Apple::new()); deliver(bag); of the vector Give ownership. } Owns Owns 22

  23. “Manual” memory management in Rust: Values owned by creator . ] Feels invisible. Values moved via assignment. When final owner returns, value is freed . 23

  24. ~ Ownership and borrowing ~ Type Ownership Alias? Mutate? ✓ T Owned ✓ &T Shared reference

  25. fn main() { fn weigh(bag: &Vec<Apple>) -> u32 { let apple = Apple::new(); … let mut bag = Vec::new(); } bag.push(apple); Shared reference bag.push(Apple::new()); to the vector let weight = weigh(&bag); … (Return type) } Loan out the bag shared references 25

  26. Sharing “freezes” data (temporarily) let mut bag = Vec::new(); bag.push(…); `bag` mutable here let r = &bag; `bag` borrowed here bag.len(); reading `bag` ok while shared cannot mutate while shared bag.push(…); ~~~~~~~~~~~ cannot mutate through shared ref r.push(…); ~~~~~~~~~ after last use of `r`, 
 bag.push(…); `bag` is mutable again 26

  27. ~ Ownership and borrowing ~ Type Ownership Alias? Mutate? ✓ T Owned ✓ &T Shared reference ✓ &mut T Mutable reference

  28. Mutable references: no other access let mut bag = Vec::new(); bag.push(…); `bag` mutable here let r = & mut bag; `bag` mutably borrowed here bag.len(); cannot access `bag` while borrowed ~~~~~~~~~ r.push(…); but can mutate through `r` bag.push(…); after last use of `r`, `bag` is accessible again 28

  29. Parallelism Photo credit: Dave Gingrich 29 https://www.flickr.com/photos/ndanger/2744507570/

  30. Observation: Building parallel abstractions is easy. Misusing those abstractions is also easy. func foo(…) { m := make(map[string]string) Go Code m[“Hello”] = “World” send data over channel channel <- m m[“Hello”] = “Data Race” but how to stop sender from 
 } using it afterwards? 30

  31. fn foo(…) { impl <T> Channel<T> { let m = HashMap::new(); fn send(& mut self , data: T) { m.insert(“Hello”, “World”); … channel.send(m); } m.insert(“Hello”, “Data Race”); } ~~~~~~~~~~~~~~~~~~~~~~~~~~ } Take ownership of the data Error: use of moved value: `book` 31

  32. ~ Concurrency paradigms ~ Paradigm Ownership? Borrowing? ✓ Message passing Fork join ✓

  33. borrowed from caller fn load_images(paths: &[PathBuf]) -> Vec<Image> { paths.iter() For each path… .map(|path| { Image::load(path) …load an image… }) .collect() …create and return a vector. } paths = [ “a.jpg”, “b.png”, …, “c.jpg” ] 33

  34. extern crate rayon; Third-party library fn load_images(paths: &[PathBuf]) -> Vec<Image> { paths.par_iter() Make it parallel .map(|path| { Image::load(path) }) .collect() } paths = [ “a.jpg”, “b.png”, …, “c.jpg” ] 34

  35. Observation: Building parallel abstractions is easy. Misusing those abstractions is also easy. 35

  36. fn load_images(paths: &[PathBuf]) -> Vec<Image> { let mut jpgs = 0; How many jpgs seen so far? paths.par_iter() If current file name ends in “jpg”… .map(|path| { if path.ends_with(“.jpg”) { jpgs += 1; } Image::load(path) …add 1 to the counter. }) .collect() } 0 0 + 1 + 1 0 1 1 1 36

  37. fn load_images(paths: &[PathBuf]) -> Vec<Image> { let mut jpgs = 0; paths.par_iter() borrows .map( ) borrows mutably .collect() mutably } |path| { |path| { if path.ends_with(“.jpg”) { if path.ends_with(“.jpg”) { ~~~~~~~~~ ❌ ~~~~~~~~~ ❌ jpgs += 1; jpgs += 1; } } Image::load(path) Image::load(path) } } 37

  38. ~ Concurrency paradigms ~ Paradigm Ownership? Borrowing? ✓ Message passing Fork join ✓ ✓ ✓ Locking ✓ ✓ Lock-free Futures ✓ ✓ …

  39. Unsafe 39

  40. Rust: An Extensible Language File R e f e m r e s n c i e l System e l c l o a u r Libraries n a t P Core Language Ownership and Borrowing 40

  41. Safe abstractions fn split_at_mut(…) { unsafe { … } Validates input, etc. } Trust me. Ownership/borrowing/traits give tools to enforce safe abstraction boundaries. 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% 42

  43. Integration Photo credit: Don Urban 43 https://www.flickr.com/photos/donpezzano/3044965125/

  44. node.js Ruby neon-bindings/neon usehelix.com CPython PyO3/pyo3 dgrunwald/rust-cpython getsentry/milksnake 44

  45. Worker ArrayBuffer 45

  46. fn callback( mut vm: CallContext) { ArrayBuffer let buffer = vm.arguments(0); let guard: VmGuard = vm.lock(); let data = buffer.borrow_mut(&guard); data .par_iter_mut() buffer .for_each(|i| *i += 1); data } borrows vm guard data 46

  47. ArrayBuffer What could go wrong? What if we invoked JS callback while using `data`? ▶ Can’t: Invoking JS callbacks requires mutable buffer borrow of `vm`! data borrow vm guard data 47

  48. https://medium.com/@wireapp/3ff37fc98c3f 48

  49. ruby! { class Console { def log(string: &str) { println!("LOG: {:?}", string); } } } 49

  50. Rust in Production 50

  51. Will discuss today! Whitepapers available online. rust-lang.org/en-US/whitepapers.html 51

  52. Category: Experienced system devs 52

  53. 53

  54. 54

Recommend


More recommend