safe, default context Trait-based polymorphism ⇒ composition instead of inheritance Enum types (tagged unions or sum types) ⇒ Option<u32>: None or Some(5) instead of NULLs ⇒ Result<T, E>: Ok(T) or Err(E) instead of exceptions Modular ⇒ crates + modules instead of includes ⇒ private by default Tooling ⇒ rustup for bootstrapping ⇒ cargo for building, testing, benchmarking, publishing, etc. Many more! ⇒ macros, pattern matching, etc.
moves (LIFO) ⇒ grows and shrinks contiguously ⇒ has a maximum size ($ ulimit -s) heap ⇒ allocation via malloc/new ⇒ deallocation via free/delete ⇒ for values that may outlive their scope ⇒ may become fragmented ⇒ practically limited by remaining vmem Process memory layout kernel vmem space static variables program text address 0 → address 0x08048000 → stack heap 32-bit address space Linux (not to scale) 1 GB 3 GB shared libs mmap region brk → %esp →
⇒ what Rust (the language) prevents unsafe examples ⇒ dereferencing an invalid pointer ⇒ dereferencing a null pointer ⇒ multiple / invalid free calls Memory unsafety vs memory leaks memory leaks ⇒ not releasing unused memory ⇒ impairs performance but behavior is defined ⇒ in some cases, possible in Rust leak examples ⇒ missing free calls ⇒ circular references
penalty ✘ behavior is difficult to predict manual calls ✔ precise control over (de)allocations ✘ prone to human errors ✘ complicates application code Memory management
penalty ✘ behavior is difficult to predict manual calls ✔ precise control over memory use ✘ prone to human errors ✘ complicates application code Memory management in Rust ⇒ ownership model ⇒ enforced by the compiler (borrow checker) ⇒ no runtime overhead
vector owns each integer let nums = vec![1, 4, 6, 4, 1]; } 1. Each value in Rust has a variable called its owner 2. There can only be one owner at a time 3. When an owner goes out of scope, all its values will be dropped
called its owner 2. There can only be one owner at a time 3. When an owner goes out of scope, all its values will be dropped struct Song { title: String, // heap-allocated string rating: u32, // unsigned 32-bit integer } fn main() { // `playlist` owns the vector // vector owns each `Song` // each `Song` owns a u32 and a String let playlist = vec![ Song { title: "Macarena".to_string(), rating: 5 }, Song { title: String::from("Smooth"), rating: 4 }, ]; }
rating: u32, // unsigned 32-bit integer } fn main() { // `playlist` owns the vector // vector owns each `Song` // each `Song` owns a u32 and a String let playlist = vec![ Song { title: "Macarena".to_string(), rating: 5 }, Song { title: String::from("Smooth"), rating: 4 }, ]; } stack heap • • 8 8 5 • 8 6 4 4 2 playlist heap M a c a r e n a capacity length capacity length heap rating title
heap-allocated string rating: u32, // unsigned 32-bit integer } fn main() { // `playlist` owns the vector // vector owns each `Song` // each `Song` owns a u32 and a String let playlist = vec![ Song { title: "Macarena".to_string(), rating: 5 }, Song { title: String::from("Smooth"), rating: 4 }, ]; } playlist Song title rating Song title rating unicode chars unicode chars vector bindings
by `nums` let nums = vec![1, 2, 1]; // vector moved to `pascal` // `pascal` now owns the vector let pascal = nums; // `nums` can not be used from this point // onwards - this is enforced by the // compiler } nums 1 pascal 2 1 vector bindings
is not allowed fn main() { let vars = vec!["foo".to_string(), "bar".to_string(), "baz".to_string()]; // this will cause compilation to fail let last = vars[2]; } vars String last String String vector unicode chars unicode chars unicode chars
main() { let vars = vec!["foo".to_string(), "bar".to_string(), "baz".to_string()]; // we could use `.remove` to make it work let last = vars.remove(2); // but this has O(n) complexity // because it shifts all remaining // elements to the left and we end up // changing the vector } Rust has move semantics vars String last String String vector unicode chars unicode chars unicode chars
main() { let vars = vec!["foo".to_string(), "bar".to_string(), "baz".to_string()]; // or we could use `.clone` let last = vars[2].clone(); // this creates a clone of the stack and // heap values, but is not always what // we want // another way is to use borrows // we'll get to that shortly } Rust has move semantics vars String last String String vector unicode chars unicode chars unicode chars String unicode chars
`Copy` types implement the `Copy` trait and can be copied bit-by-bit fn main() { let x = true; let y = x; assert!(x); let nums = vec![6, 28, 496]; let last = nums[2]; assert_eq!(last, 496); println!("we have {} items", nums.len()); // stdout: we have 3 items } nums last vector 6 28 496 496 x y true true
semantics fn sum(input: Vec<i32>) -> i32 { let mut result = 0; // loops also obey move semantics for n in input { result += n; } result } fn main() { let nums = vec![1, 2, 3]; let result = sum(nums); println!("sum is {}", result); // can't use `nums` anymore println!("from {} items", nums.len()); }
use of moved value: `nums` --> src/main.rs:13:34 | 13 | let result = sum(nums); | ---- value moved here ... 16 | println!("from {} items", nums.len()); | ^^^^ value used here after move | = note: move occurs because `nums` has type `std::vec::Vec<i32>`, which does not implement the `Copy` trait error: aborting due to previous error(s) error: Could not compile `app`. To learn more, run the command again with --verbose. $ _ // function calls obey move semantics fn sum(input: Vec<i32>) -> i32 { let mut result = 0; // loops also obey move semantics for n in input { result += n; } result } fn main() { let nums = vec![1, 2, 3]; let result = sum(nums); println!("sum is {}", result); // can't use `nums` anymore println!("from {} items", nums.len()); }
&Vec<i32>) -> i32 { let mut result = 0; // loops also obey move semantics for n in input { result += *n; } result } fn main() { let nums = vec![1, 2, 3]; let result = sum(&nums); println!("sum is {}", result); // `nums` is accessible now println!("from {} items", nums.len()); } $ cargo run↲ Compiling app v0.1.0 (file:///path/to/app) Finished dev [unoptimized + debuginfo] target(s) in 0.46 secs Running `target/debug/app` sum is 6 from 3 items $ _
given time, you can only have either: a. one mutable reference b. any number of shared references Ownership rules & borrowing rules 1. Each value in Rust has a variable called its owner 2. There can only be one owner at a time 3. When an owner goes out of scope, all its values will be dropped
⇒ referent can be borrowed multiple times ⇒ freezes paths to and from referent during the borrow ⇒ for type T, its shared borrow is type &T (ref T) Two kinds of borrows mutable borrow ⇒ reference that can update its referent ⇒ created from mutable bindings ⇒ exclusive access to referent ⇒ makes paths to and from referent inaccessible during the borrow ⇒ for type T, its mutable borrow is type &mut T (ref mut T)
x = 10; let s1 = &x; let s2 = &x; } $ cargo build↲ Compiling app v0.1.0 (file:///path/to/app) Finished dev [unoptimized + debuginfo] target(s) in 0.32 secs A taste of the borrow checker // shared borrows freezes their referent fn main() { let mut x = 10; let s1 = &x; let s2 = &x; x += 3; } $ cargo build↲ Compiling app v0.1.0 (file:///path/to/app) error[E0506]: cannot assign to `x` because it is borrowed --> src/main.rs:5:5 | 3 | let s1 = &x; | - borrow of `x` occurs here 4 | let s2 = &x; 5 | x += 3; | ^^^^^^^ assignment to borrowed `x` occurs here error: aborting due to previous error(s) error: Could not compile `rust-sandbox`. To learn more, run the command again with --verbose.
mut x = 10; let m1 = &mut x; let m2 = &mut x; } $ cargo build↲ Compiling app v0.1.0 (file:///path/to/app) error[E0499]: cannot borrow `x` as mutable more than once at a time --> src/main.rs:4:19 | 3 | let m1 = &mut x; | - first mutable borrow occurs here 4 | let m2 = &mut x; | ^ second mutable borrow occurs here 5 | } | - first borrow ends here error: aborting due to previous error(s) error: Could not compile `rust-sandbox`. A taste of the borrow checker // mutable borrows can modify the referent fn main() { let mut x = 10; let m1 = &mut x; *m1 = 7; } $ cargo build↲ Compiling app v0.1.0 (file:///path/to/app) Finished dev [unoptimized + debuginfo] target(s) in 0.66 secs
{ host: String, theme: Song } fn main() { let mut party = Party { host: "me".to_string(), theme: Song { title: "Macarena".to_string(), rating: 5 } }; let karaoke = &party.theme.title; // error: cannot borrow `party` as mutable // because `party.theme.title` is also // borrowed as immutable let new_party = &mut party; } bindings Shared borrow freezes ancestors & descendants party host theme Party karaoke String unicode chars Song title rating unicode chars &
{ host: String, theme: Song } fn main() { let mut party = Party { host: "me".to_string(), theme: Song { title: "Macarena".to_string(), rating: 5 } }; let karaoke = &party.theme.title; // error: cannot move out of `party.theme` because // it is borrowed let fav_song = party.theme; } bindings Shared borrow freezes ancestors & descendants party host theme Party karaoke String unicode chars Song title rating unicode chars &
party host theme Party karaoke String unicode chars Song title rating unicode chars &mut struct Song { title: String, rating: u32 } struct Party { host: String, theme: Song } fn main() { let mut party = Party { host: "me".to_string(), theme: Song { title: "Macarena".to_string(), rating: 5 } }; let karaoke = &mut party.theme.title; }
party host theme Party karaoke String unicode chars Song title rating unicode chars &mut struct Song { title: String, rating: u32 } struct Party { host: String, theme: Song } fn main() { let mut party = Party { host: "me".to_string(), theme: Song { title: "Macarena".to_string(), rating: 5 } }; let karaoke = &mut party.theme.title; // this would be ok if `karaoke` was a shared borrow // error: cannot borrow `party` as immutable // because `party.theme.title` is also // borrowed as mutable let current_party = &party; }
party host theme Party karaoke String unicode chars Song title rating unicode chars &mut struct Song { title: String, rating: u32 } struct Party { host: String, theme: Song } fn main() { let mut party = Party { host: "me".to_string(), theme: Song { title: "Macarena".to_string(), rating: 5 } }; let karaoke = &mut party.theme.title; // this is still ok let mut next_host = party.host; next_host.push('?'); }
'b // | { // | let x = 5; // -+----- 'a r = &x; // | | } // -+ | // | println!("ref is {}", r); // | // | // ----+ } $ cargo run↲ Compiling app v0.1.0 (file:///path/to/app) error[E0597]: `x` does not live long enough --> src/main.rs:8:9 | 7 | r = &x; // | | | - borrow occurs here 8 | } // -+ | | ^ `x` dropped here while still borrowed ... 13 | } | - borrowed value needs to live until here error: aborting due to previous error(s) error: Could not compile `app`. To learn more, run the command again with --verbose. { let x = 5; // -----+-- 'a // | let r = &x; // --+----- 'b // | | println!("ref is {}", r); // | | // --+ | } // -----+ $ cargo run↲ Compiling app v0.1.0 (file:///path/to/app) Finished dev [unoptimized + debuginfo] target(s) in 0.43 secs Running `target/debug/app` ref: 5
2. call unsafe functions 3. access and modify mutable static variables 4. implement an unsafe trait Why? Because the borrow checker favors correctness unsafe Rust // splitting a mutable slice into two mutable slices // impossible in safe Rust - because of two mutable borrows use std::slice; fn split_at_mut( slice: &mut [i32], mid: usize ) -> (&mut [i32], &mut [i32]) { let len = slice.len(); let ptr = slice.as_mut_ptr(); assert!(mid <= len); let offset = mid as isize; let end = len - mid; unsafe { (slice::from_raw_parts_mut(ptr, mid), slice::from_raw_parts_mut(ptr.offset(offset), end)) } }
Jim Blandy ⇒ The Rust Programming Language book by Steve Klabnik and Carol Nichols ⇒ Memory leaks are memory safe blog post by Huon Wilson ⇒ Front photo: Marta Pawlik ⇒ This photo: Jeremy Bishop Personal experiments ⇒ https://github.com/bow/gtetools: CLI tool for gene annotation formats ⇒ https://git.lumc.nl/hem/fidus: FLT3 ITD detection tool Other projects of note: ⇒ https://github.com/rust-bio/rust-bio: bioinformatics library ⇒ https://github.com/rust-bio/rust-htslib: wrapper for the htslib C library