Show pageOld revisionsBacklinksBack to top This page is read only. You can view the source, but not change it. Ask your administrator if you think this is wrong. ====== The Rust Programming Language ====== **source**: https://doc.rust-lang.org/book/title-page.html ===== Additional resources ===== * [[https://veykril.github.io/tlborm/|The Little Book of Rust Macros]] * [[https://doc.rust-lang.org/nomicon/intro.html|The Rustonomicon book]] * [[https://doc.rust-lang.org/reference/index.html|Rust reference book]] * [[https://doc.rust-lang.org/std/collections/index.html|Documentation on all collections]] * [[https://docs.rs/quote/latest/quote/|'quote' crate documentation]] * [[https://doc.rust-lang.org/reference/keywords.html|List of rust keywords]] * [[https://docs.rust-embedded.org/book/intro/index.html|The Embedded Rust Book]] * [[https://nnethercote.github.io/perf-book/title-page.html|The Rust Performance Book]] * [[https://rust-lang.github.io/async-book/01_getting_started/01_chapter.html|Asynchronous Programming in Rust]] ===== 1 - Getting started ===== * **link**: https://doc.rust-lang.org/book/ch01-00-getting-started.html ===== 2 - Programming a Guessing Game ===== * **link**: https://doc.rust-lang.org/book/ch02-00-guessing-game-tutorial.html ===== 3 - Common Programming concepts ===== * **link**: https://doc.rust-lang.org/book/ch03-00-common-programming-concepts.html ===== 4 - Ownership in Rust ===== * **link**: https://doc.rust-lang.org/book/ch04-01-what-is-ownership.html * Rules of ownership: * Each value in Rust has a variable that’s called its owner. * There can only be one owner at a time. * When the owner goes out of scope, the value will be dropped. * Rules of references: * At any given time, you can have either one mutable reference or any number of immutable references. * References must always be valid. * Sting slice type is: **&str** * Array slice type is for instance **&[i32]** * Example of array slice: <sxh rust; highlight: []>let a = [1, 2, 3, 4, 5]; let slice = &a[1..3];</sxh> ===== 5 - Using Structs to Structure Related Data ===== * **link**: https://doc.rust-lang.org/book/ch05-00-structs.html * Struct fields init shorthand: <sxh rust; highlight: []>fn build_user(email: String, username: String) -> User { User { email, username, active: true, sign_in_count: 1, } }</sxh> * Struct update syntax: <sxh rust; highlight: []> let user2 = User { email: String::from("another@example.com"), ..user1 };</sxh> * Tuple structs: <sxh rust; highlight: []> struct Color(i32, i32, i32); struct Point(i32, i32, i32); let black = Color(0, 0, 0); let origin = Point(0, 0, 0);</sxh> * Unit-like structs: <sxh rust; highlight: []> struct AlwaysEqual; let subject = AlwaysEqual;</sxh> * Usage of **dbg!** macro: <sxh rust; highlight: []>#[derive(Debug)] struct Rectangle { width: u32, height: u32, } fn main() { let scale = 2; let rect1 = Rectangle { width: dbg!(30 * scale), height: 50, }; dbg!(&rect1); }</sxh> * List of attributes that can be used in Rust: https://doc.rust-lang.org/reference/attributes.html * **Note**: we can have multiple **impl** block for a given struct. ===== 6 - Enums and Pattern Matching ===== * **link**: https://doc.rust-lang.org/book/ch06-00-enums.html * We can put any kind of data in enums: <sxh rust; highlight: []>struct Ipv4Addr { // --snip-- } struct Ipv6Addr { // --snip-- } enum IpAddr { V4(Ipv4Addr), V6(Ipv6Addr), } enum Message { Quit, Move { x: i32, y: i32 }, Write(String), ChangeColor(i32, i32, i32), }</sxh> * We can provide **impl** on enums. * The option enum to use as a replacement for the "null" concept: <sxh rust; highlight: []>enum Option<T> { None, Some(T), }</sxh> * Documentation on Option<T>: https://doc.rust-lang.org/std/option/enum.Option.html * Catch-all Patterns and the underscore Placeholder: <sxh rust; highlight: []> let dice_roll = 9; match dice_roll { 3 => add_fancy_hat(), 7 => remove_fancy_hat(), _ => (), // empty tuple here == 'unit value' } fn add_fancy_hat() {} fn remove_fancy_hat() {}</sxh> * usage of **if let** an syntaxtic sugar for match: <sxh rust; highlight: []> let mut count = 0; if let Coin::Quarter(state) = coin { println!("State quarter from {:?}!", state); } else { count += 1; }</sxh> ===== 7 - Managing Growing Projects with Packages, Crates, and Modules ===== * **link**: https://doc.rust-lang.org/book/ch07-00-managing-growing-projects-with-packages-crates-and-modules.html * To bring a function into the scope it is idiomatic to bring the parent module of that function: <sxh rust; highlight: []>mod front_of_house { pub mod hosting { pub fn add_to_waitlist() {} } } use self::front_of_house::hosting; pub fn eat_at_restaurant() { hosting::add_to_waitlist(); hosting::add_to_waitlist(); hosting::add_to_waitlist(); }</sxh> * To bring a struct, enums, etc it is idiomatic to bring the full path (expect if there is a conflict): <sxh rust; highlight: []>use std::collections::HashMap; fn main() { let mut map = HashMap::new(); map.insert(1, 2); }</sxh> * We can use **as** to resolve a name conflict: <sxh rust; highlight: []>use std::fmt::Result; use std::io::Result as IoResult; fn function1() -> Result { // --snip-- } fn function2() -> IoResult<()> { // --snip-- }</sxh> * We can re-export use paths with pub: <sxh rust; highlight: []>mod front_of_house { pub mod hosting { pub fn add_to_waitlist() {} } } pub use crate::front_of_house::hosting; pub fn eat_at_restaurant() { hosting::add_to_waitlist(); hosting::add_to_waitlist(); hosting::add_to_waitlist(); }</sxh> * Can use nested pathswith **use**: <sxh rust; highlight: []>use std::{cmp::Ordering, io}; use std::io::{self, Write};</sxh> * Usage of glob operator to bring all public items: <sxh rust; highlight: []>use std::collections::*;</sxh> * We will talk about testing in chapter 11: https://doc.rust-lang.org/book/ch11-01-writing-tests.html#how-to-write-tests * We can move modules in their own files, and then specify that we use them with: <sxh rust; highlight: []>mod front_of_house; pub use crate::front_of_house::hosting; pub fn eat_at_restaurant() { hosting::add_to_waitlist(); hosting::add_to_waitlist(); hosting::add_to_waitlist(); }</sxh> ===== 8 - Common Collections ===== * **link**: https://doc.rust-lang.org/book/ch08-00-common-collections.html * Vector usage: <sxh rust; highlight: []>// Create a new empty vector: let v: Vec<i32> = Vec::new(); // Or with the vec! macro: let v = vec![1, 2, 3]; // Add values in a vector: let mut v = Vec::new(); v.push(5); v.push(6); </sxh>/*//*/ * When the vector gets dropped, all of its contents are also dropped * get the values in a vector: <sxh rust; highlight: []> let v = vec![1, 2, 3, 4, 5]; let third: &i32 = &v[2]; println!("The third element is {}", third); match v.get(2) { Some(third) => println!("The third element is {}", third), None => println!("There is no third element."), }</sxh> * Using an enum to store multiple types: <sxh rust; highlight: []> enum SpreadsheetCell { Int(i32), Float(f64), Text(String), } let row = vec![ SpreadsheetCell::Int(3), SpreadsheetCell::Text(String::from("blue")), SpreadsheetCell::Float(10.12), ];</sxh> * Vector API documentation: https://doc.rust-lang.org/std/vec/struct.Vec.html * Rust has only one string type in the core language, which is the string slice str that is usually seen in its borrowed form &str * Example of HashMap update: <sxh rust; highlight: []> use std::collections::HashMap; let text = "hello world wonderful world"; let mut map = HashMap::new(); for word in text.split_whitespace() { let count = map.entry(word).or_insert(0); *count += 1; } println!("{:?}", map);</sxh> ===== 9 - Error Handling ===== * **linlk**: https://doc.rust-lang.org/book/ch09-00-error-handling.html * To disable unwinding at release for instance, we can add in Cargo.toml: <code>[profile.release] panic = 'abort'</code> * Manually calling panic!: <sxh rust; highlight: []>fn main() { panic!("crash and burn"); }</sxh> * We can set the **RUST_BACKTRACE=1** env. variable to get backtrace display on panic or even **RUST_BACKTRACE=full** for full report. * In order to get backtraces with this information, debug symbols must be enabled. Debug symbols are enabled by default when using cargo build or cargo run without the --release flag * We can use Result to handle non fatal errors: <sxh rust; highlight: []>enum Result<T, E> { Ok(T), Err(E), }</sxh> * More advanced matching with inner matches: <sxh rust; highlight: []>use std::fs::File; use std::io::ErrorKind; fn main() { let f = File::open("hello.txt"); let f = match f { Ok(file) => file, Err(error) => match error.kind() { ErrorKind::NotFound => match File::create("hello.txt") { Ok(fc) => fc, Err(e) => panic!("Problem creating the file: {:?}", e), }, other_error => { panic!("Problem opening the file: {:?}", other_error) } }, }; }</sxh> * Alternate code with closure: <sxh rust; highlight: []>use std::fs::File; use std::io::ErrorKind; fn main() { let f = File::open("hello.txt").unwrap_or_else(|error| { if error.kind() == ErrorKind::NotFound { File::create("hello.txt").unwrap_or_else(|error| { panic!("Problem creating the file: {:?}", error); }) } else { panic!("Problem opening the file: {:?}", error); } }); }</sxh> * Propagating errors: <sxh rust; highlight: []>use std::fs::File; use std::io::{self, Read}; fn read_username_from_file() -> Result<String, io::Error> { let f = File::open("hello.txt"); let mut f = match f { Ok(file) => file, Err(e) => return Err(e), }; let mut s = String::new(); match f.read_to_string(&mut s) { Ok(_) => Ok(s), Err(e) => Err(e), } }</sxh> * Question mark shortcut to propagate errors: <sxh rust; highlight: []>use std::fs::File; use std::io; use std::io::Read; fn read_username_from_file() -> Result<String, io::Error> { let mut f = File::open("hello.txt")?; let mut s = String::new(); f.read_to_string(&mut s)?; Ok(s) }</sxh> * haéndling errors directly in main: <sxh rust; highlight: []>use std::error::Error; use std::fs::File; fn main() -> Result<(), Box<dyn Error>> { let f = File::open("hello.txt")?; Ok(()) }</sxh> ===== 10 - Generic Types, Traits, and Lifetimes ===== * **link**: https://doc.rust-lang.org/book/ch10-00-generics.html * Struct using generics: <sxh rust; highlight: []>struct Point<T> { x: T, y: T, } impl<T> Point<T> { fn x(&self) -> &T { &self.x } } // Type specific implementation: impl Point<f32> { fn distance_from_origin(&self) -> f32 { (self.x.powi(2) + self.y.powi(2)).sqrt() } } // or with multiple generic types: struct Point<T, U> { x: T, y: U, } </sxh>/*////*/ * Enums using generics: <sxh rust; highlight: []>enum Option<T> { Some(T), None, } enum Result<T, E> { Ok(T), Err(E), }</sxh> * Example of generic method in generic struct impl: <sxh rust; highlight: []>struct Point<X1, Y1> { x: X1, y: Y1, } impl<X1, Y1> Point<X1, Y1> { fn mixup<X2, Y2>(self, other: Point<X2, Y2>) -> Point<X1, Y2> { Point { x: self.x, y: other.y, } } }</sxh> * Rust performs **monomorphization** of the generics => no performance cost at runtime. * Declaring a trait: <sxh rust; highlight: []>pub trait Summary { fn summarize(&self) -> String; }</sxh> * Implementing a trait on a type: <sxh rust; highlight: []>pub struct NewsArticle { pub headline: String, pub location: String, pub author: String, pub content: String, } impl Summary for NewsArticle { fn summarize(&self) -> String { format!("{}, by {} ({})", self.headline, self.author, self.location) } } pub struct Tweet { pub username: String, pub content: String, pub reply: bool, pub retweet: bool, } impl Summary for Tweet { fn summarize(&self) -> String { format!("{}: {}", self.username, self.content) } }</sxh> * Default method implementation in trait: <sxh rust; highlight: []>pub trait Summary { fn summarize(&self) -> String { String::from("(Read more...)") } } impl Summary for NewsArticle {}</sxh> * Using trait as parameter: <sxh rust; highlight: []>pub fn notify(item: &impl Summary) { println!("Breaking news! {}", item.summarize()); } // or with trait bound syntax: pub fn notify<T: Summary>(item: &T) { println!("Breaking news! {}", item.summarize()); }</sxh>/*//*/ * Multiple Trait Bounds with the + Syntax: <sxh rust; highlight: []>pub fn notify(item: &(impl Summary + Display)) {} // or pub fn notify<T: Summary + Display>(item: &T) {} </sxh>/*//*/ * Trait Bounds with where Clauses: <sxh rust; highlight: []>fn some_function<T, U>(t: &T, u: &U) -> i32 where T: Display + Clone, U: Clone + Debug {}</sxh> * Returning Types that Implement Traits: <sxh rust; highlight: []>fn returns_summarizable() -> impl Summary { Tweet { username: String::from("horse_ebooks"), content: String::from( "of course, as you probably already know, people", ), reply: false, retweet: false, } }</sxh> * => However, you can only use impl Trait if you’re returning a single type * Conditional method implementation with traits: <sxh rust; highlight: []>use std::fmt::Display; struct Pair<T> { x: T, y: T, } impl<T> Pair<T> { fn new(x: T, y: T) -> Self { Self { x, y } } } impl<T: Display + PartialOrd> Pair<T> { fn cmp_display(&self) { if self.x >= self.y { println!("The largest member is x = {}", self.x); } else { println!("The largest member is y = {}", self.y); } } }</sxh> * We can also conditionally implement a trait for any type that implements another trait. Implementations of a trait on any type that satisfies the trait bounds are called blanket implementations: <sxh rust; highlight: []>impl<T: Display> ToString for T { // --snip-- }</sxh> * lifetime annotation syntax: <sxh rust; highlight: []>&i32 // a reference &'a i32 // a reference with an explicit lifetime &'a mut i32 // a mutable reference with an explicit lifetime</sxh>/*//*/ * Lifetime Annotations in Function Signatures: <sxh rust; highlight: []>fn longest<'a>(x: &'a str, y: &'a str) -> &'a str { if x.len() > y.len() { x } else { y } }</sxh> * Lifetime Annotations in Struct Definitions: <sxh rust; highlight: []>struct ImportantExcerpt<'a> { part: &'a str, } fn main() { let novel = String::from("Call me Ishmael. Some years ago..."); let first_sentence = novel.split('.').next().expect("Could not find a '.'"); let i = ImportantExcerpt { part: first_sentence, }; }</sxh> * Can have multiple lifetimes in function declaration: <sxh rust; highlight: []>fn foo<'a, 'b>(x: &'a i32, y: &'b i32);</sxh> * The patterns programmed into Rust’s analysis of references are called the lifetime elision rules * Static lifetime: <sxh rust; highlight: []>let s: &'static str = "I have a static lifetime.";</sxh> * **TODO** it's not really clear yet with the third lifetime elision rule would **not** introduce an invalid reference we start with that kind of code: <sxh rust; highlight: []>impl<'a> ImportantExcerpt<'a> { fn announce_and_return_part(&self, announcement: &str) -> &str { println!("Attention please: {}", announcement); self.part } }</sxh> * Generic Type Parameters, Trait Bounds, and Lifetimes Together: <sxh rust; highlight: []>use std::fmt::Display; fn longest_with_an_announcement<'a, T>( x: &'a str, y: &'a str, ann: T, ) -> &'a str where T: Display, { println!("Announcement! {}", ann); if x.len() > y.len() { x } else { y } }</sxh> ===== 11 - Writing Automated Tests ===== * **link**: https://doc.rust-lang.org/book/ch11-00-testing.html * A simple test function in a test module: <sxh rust; highlight: []>#[cfg(test)] mod tests { #[test] fn it_works() { assert_eq!(2 + 2, 4); } }</sxh> * To run the tests we use the command: <sxh bash; highlight: []>$ cargo test</sxh> * Tests can use **panic!**: <sxh rust; highlight: []>#[cfg(test)] mod tests { #[test] fn exploration() { assert_eq!(2 + 2, 4); } #[test] fn another() { panic!("Make this test fail"); } }</sxh> * Tests can use **assert!**: <sxh rust; highlight: []>#[cfg(test)] mod tests { use super::*; #[test] fn larger_can_hold_smaller() { let larger = Rectangle { width: 8, height: 7, }; let smaller = Rectangle { width: 5, height: 1, }; assert!(larger.can_hold(&smaller)); } }</sxh> * Other available test macros: **assert_eq!** and **assert_ne!** * Custom failure messages: <sxh rust; highlight: []> #[test] fn greeting_contains_name() { let result = greeting("Carol"); assert!( result.contains("Carol"), "Greeting did not contain name, value was `{}`", result ); }</sxh> * Can check for panics with **should_panic**: <sxh rust; highlight: []>pub struct Guess { value: i32, } impl Guess { pub fn new(value: i32) -> Guess { if value < 1 || value > 100 { panic!("Guess value must be between 1 and 100, got {}.", value); } Guess { value } } } #[cfg(test)] mod tests { use super::*; #[test] #[should_panic] fn greater_than_100() { Guess::new(200); } } // And we can also specify a partial panic message: #[cfg(test)] mod tests { use super::*; #[test] #[should_panic(expected = "Guess value must be less than or equal to 100")] fn greater_than_100() { Guess::new(200); } } </sxh> /*//*/ * Can use **Result<T, E>** as test return value: <sxh rust; highlight: []>#[cfg(test)] mod tests { #[test] fn it_works() -> Result<(), String> { if 2 + 2 == 4 { Ok(()) } else { Err(String::from("two plus two does not equal four")) } } }</sxh> * Note: can’t use the #[should_panic] annotation on tests that use Result<T, E>. * To run threads consecutively: <sxh bash; highlight: []>$ cargo test -- --test-threads=1</sxh> * To show function outputs from tests: <sxh bash; highlight: []>$ cargo test -- --show-output</sxh> * Select tests by name: <sxh rust; highlight: []>pub fn add_two(a: i32) -> i32 { a + 2 } #[cfg(test)] mod tests { use super::*; #[test] fn add_two_and_two() { assert_eq!(4, add_two(2)); } #[test] fn add_three_and_two() { assert_eq!(5, add_two(3)); } #[test] fn one_hundred() { assert_eq!(102, add_two(100)); } }</sxh> * then we can specify 'partial names': <sxh bash; highlight: []>$ cargo test one_hundred Compiling adder v0.1.0 (file:///projects/adder) Finished test [unoptimized + debuginfo] target(s) in 0.69s Running unittests (target/debug/deps/adder-92948b65e88960b4) running 1 test test tests::one_hundred ... ok test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 2 filtered out; finished in 0.00s $ cargo test add Compiling adder v0.1.0 (file:///projects/adder) Finished test [unoptimized + debuginfo] target(s) in 0.61s Running unittests (target/debug/deps/adder-92948b65e88960b4) running 2 tests test tests::add_three_and_two ... ok test tests::add_two_and_two ... ok test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 1 filtered out; finished in 0.00s </sxh> * Can ignore expensive test by default with: <sxh rust; highlight: []>#[test] fn it_works() { assert_eq!(2 + 2, 4); } #[test] #[ignore] fn expensive_test() { // code that takes an hour to run }</sxh> * And then run them with: <sxh bash; highlight: []>$ cargo test -- --ignored</sxh> * And we can run **all** tests (ignored or not) with: <sxh bash; highlight: []>cargo test -- --include-ignored</sxh> * **Unit tests** are small and more focused, testing one module in isolation at a time, and can test private interfaces. **Integration tests** are entirely external to your library and use your code in the same way any other external code would, using only the public interface and potentially exercising multiple modules per test. * For unit tests: we will put unit tests in the src directory in each file with the code that they’re testing. The convention is to create a module named tests in each file to contain the test functions and to annotate the module with cfg(test). * Integration tests go in a different directory, they don’t need the #[cfg(test)] annotation. However, because unit tests go in the same files as the code, you’ll use #[cfg(test)] to specify that they shouldn’t be included in the compiled result. * In Rust we can test private functions: <sxh rust; highlight: []>pub fn add_two(a: i32) -> i32 { internal_adder(a, 2) } fn internal_adder(a: i32, b: i32) -> i32 { a + b } #[cfg(test)] mod tests { use super::*; #[test] fn internal() { assert_eq!(4, internal_adder(2, 2)); } }</sxh> * Integration tests should be placed in a **tests/** directory sibling to the project **src/** directory. * We can then make as many test files as we want to in this directory, and Cargo will compile each of the files as an individual crate. * For instance when builder a project called "adder" we could define this integration test: <sxh rust; highlight: []>use adder; #[test] fn it_adds_two() { assert_eq!(4, adder::add_two(2)); }</sxh> * To run all the tests in a particular integration test file: <sxh rust; highlight: []>$ cargo test --test integration_test Compiling adder v0.1.0 (file:///projects/adder) Finished test [unoptimized + debuginfo] target(s) in 0.64s Running tests/integration_test.rs (target/debug/deps/integration_test-82e7799c1bc62298) </sxh> * To create submodules in the tests folder we have to call those modules for instance **tests/common/mod.rs** instead of **tests/common.rs** * If our project is a binary crate that only contains a src/main.rs file and doesn’t have a src/lib.rs file, we can’t create integration tests in the tests directory and bring functions defined in the src/main.rs file into scope with a use statement. * This is one of the reasons Rust projects that provide a binary have a straightforward src/main.rs file that calls logic that lives in the src/lib.rs file. Using that structure, integration tests can test the library crate with use to make the important functionality available. If the important functionality works, the small amount of code in the src/main.rs file will work as well, and that small amount of code doesn’t need to be tested. ===== 12 - An I/O Project: Building a Command Line Program ===== * **link**: https://doc.rust-lang.org/book/ch12-00-an-io-project.html * **Test-driven development (TDD) process**: This software development technique follows these steps: - Write a test that fails and run it to make sure it fails for the reason you expect. - Write or modify just enough code to make the new test pass. - Refactor the code you just added or changed and make sure the tests continue to pass. - Repeat from step 1! * Minimal skeleton for minigrep app: <sxh rust; highlight: []>// src/main.rs use std::env; use std::process; use minigrep::Config; fn main() { // --snip-- if let Err(e) = minigrep::run(config) { // --snip-- } } // src/lib.rs use std::error::Error; use std::fs; pub struct Config { pub query: String, pub filename: String, } impl Config { pub fn new(args: &[String]) -> Result<Config, &'static str> { if args.len() < 3 { return Err("not enough arguments"); } let query = args[1].clone(); let filename = args[2].clone(); Ok(Config { query, filename }) } } pub fn run(config: Config) -> Result<(), Box<dyn Error>> { let contents = fs::read_to_string(config.filename)?; for line in search(&config.query, &contents) { println!("{}", line); } Ok(()) } pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> { let mut results = Vec::new(); for line in contents.lines() { if line.contains(query) { results.push(line); } } results } </sxh> /*//*/ * Checking for an environment variable: <sxh rust; highlight: []>use std::env; // --snip-- impl Config { pub fn new(args: &[String]) -> Result<Config, &'static str> { if args.len() < 3 { return Err("not enough arguments"); } let query = args[1].clone(); let filename = args[2].clone(); let case_sensitive = env::var("CASE_INSENSITIVE").is_err(); Ok(Config { query, filename, case_sensitive, }) } }</sxh> /*//*/ * To print messages on stderr: <sxh rust; highlight: []>fn main() { let args: Vec<String> = env::args().collect(); let config = Config::new(&args).unwrap_or_else(|err| { eprintln!("Problem parsing arguments: {}", err); process::exit(1); }); if let Err(e) = minigrep::run(config) { eprintln!("Application error: {}", e); process::exit(1); } }</sxh> ===== 13 - Functional Language Features: Iterators and Closures ===== * **link**: https://doc.rust-lang.org/book/ch13-00-functional-features.html * Sleeping on a thread: <sxh rust; highlight: []>use std::thread; use std::time::Duration; fn simulated_expensive_calculation(intensity: u32) -> u32 { println!("calculating slowly..."); thread::sleep(Duration::from_secs(2)); intensity }</sxh> * Example of closure definition: <sxh rust; highlight: []>fn generate_workout(intensity: u32, random_number: u32) { let expensive_closure = |num| { println!("calculating slowly..."); thread::sleep(Duration::from_secs(2)); num }; if intensity < 25 { println!("Today, do {} pushups!", expensive_closure(intensity)); println!("Next, do {} situps!", expensive_closure(intensity)); } else { if random_number == 3 { println!("Take a break today! Remember to stay hydrated!"); } else { println!( "Today, run for {} minutes!", expensive_closure(intensity) ); } } }</sxh> * Optional elements in closure syntax: <sxh rust; highlight: []>fn add_one_v1 (x: u32) -> u32 { x + 1 } let add_one_v2 = |x: u32| -> u32 { x + 1 }; let add_one_v3 = |x| { x + 1 }; let add_one_v4 = |x| x + 1 ;</sxh> * Closure definitions will have one concrete type inferred for each of their parameters and for their return value. (ie. cannot used different types of parameters with different calls.) * Storing closures using the generic pattern and the Fn trait: <sxh rust; highlight: []>impl<T> Cacher<T> where T: Fn(u32) -> u32, { fn new(calculation: T) -> Cacher<T> { Cacher { calculation, value: None, } } fn value(&mut self, arg: u32) -> u32 { match self.value { Some(v) => v, None => { let v = (self.calculation)(arg); self.value = Some(v); v } } } }</sxh> * Then we could use the Cacher struct above as follow for instance: <sxh rust; highlight: []> let mut expensive_result = Cacher::new(|num| { println!("calculating slowly..."); thread::sleep(Duration::from_secs(2)); num }); </sxh> * Example closure capturing environmnet: <sxh rust; highlight: []>fn main() { let x = 4; let equal_to_x = |z| z == x; let y = 4; assert!(equal_to_x(y)); }</sxh> * If you want to force the closure to take ownership of the values it uses in the environment, you can use the move keyword before the parameter list. This technique is mostly useful when passing a closure to a new thread to move the data so it’s owned by the new thread. * Example of closure with move: <sxh rust; highlight: []> // This will not compile. fn main() { let x = vec![1, 2, 3]; let equal_to_x = move |z| z == x; println!("can't use x here: {:?}", x); let y = vec![1, 2, 3]; assert!(equal_to_x(y)); }</sxh> /*////*/ * Creating an iterator: <sxh rust; highlight: []> let v1 = vec![1, 2, 3]; let v1_iter = v1.iter(); for val in v1_iter { println!("Got: {}", val); }</sxh> * Iterator trait: <sxh rust; highlight: []>pub trait Iterator { type Item; fn next(&mut self) -> Option<Self::Item>; // methods with default implementations elided }</sxh> * Chaining iterators: <sxh rust; highlight: []> let v1: Vec<i32> = vec![1, 2, 3]; let v2: Vec<_> = v1.iter().map(|x| x + 1).collect(); assert_eq!(v2, vec![2, 3, 4]);</sxh> * the filter iterator adaptor example: <sxh rust; highlight: []>#[derive(PartialEq, Debug)] struct Shoe { size: u32, style: String, } fn shoes_in_size(shoes: Vec<Shoe>, shoe_size: u32) -> Vec<Shoe> { shoes.into_iter().filter(|s| s.size == shoe_size).collect() } #[cfg(test)] mod tests { use super::*; #[test] fn filters_by_size() { let shoes = vec![ Shoe { size: 10, style: String::from("sneaker"), }, Shoe { size: 13, style: String::from("sandal"), }, Shoe { size: 10, style: String::from("boot"), }, ]; let in_my_size = shoes_in_size(shoes, 10); assert_eq!( in_my_size, vec![ Shoe { size: 10, style: String::from("sneaker") }, Shoe { size: 10, style: String::from("boot") }, ] ); } }</sxh> * Implementing an iterator: <sxh rust; highlight: []>struct Counter { count: u32, } impl Counter { fn new() -> Counter { Counter { count: 0 } } } impl Iterator for Counter { type Item = u32; fn next(&mut self) -> Option<Self::Item> { if self.count < 5 { self.count += 1; Some(self.count) } else { None } } }</sxh> * Example of advanced iterators usage: <sxh rust; highlight: []> #[test] fn using_other_iterator_trait_methods() { let sum: u32 = Counter::new() .zip(Counter::new().skip(1)) .map(|(a, b)| a * b) .filter(|x| x % 3 == 0) .sum(); assert_eq!(18, sum); }</sxh> * Using iterators to get env args: <sxh rust; highlight: []>impl Config { pub fn new(mut args: env::Args) -> Result<Config, &'static str> { args.next(); let query = match args.next() { Some(arg) => arg, None => return Err("Didn't get a query string"), }; let filename = match args.next() { Some(arg) => arg, None => return Err("Didn't get a file name"), }; let case_sensitive = env::var("CASE_INSENSITIVE").is_err(); Ok(Config { query, filename, case_sensitive, }) } }</sxh> * Usint iterators to search in minigrep app: <sxh rust; highlight: []>pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> { contents .lines() .filter(|line| line.contains(query)) .collect() }</sxh> ===== 14 - More About Cargo and Crates.io ===== * **link**: https://doc.rust-lang.org/book/ch14-00-more-about-cargo.html * Cargo documentation: https://doc.rust-lang.org/cargo/ * Different release profiles: <sxh rust; highlight: []>$ cargo build Finished dev [unoptimized + debuginfo] target(s) in 0.0s $ cargo build --release Finished release [optimized] target(s) in 0.0s</sxh> * Can set profile settings in cargo.toml file: <code>[profile.dev] opt-level = 0 [profile.release] opt-level = 3</code> * All cargo profile config options: https://doc.rust-lang.org/cargo/reference/profiles.html * Documentation comments use three slashes, <nowiki>///</nowiki> /*//*/, instead of two and support Markdown notation for formatting the text. Place documentation comments just before the item they’re documenting: <sxh rust; highlight: []>/// Adds one to the number given. /// /// # Examples /// /// ``` /// let arg = 5; /// let answer = my_crate::add_one(arg); /// /// assert_eq!(6, answer); /// ``` pub fn add_one(x: i32) -> i32 { x + 1 }</sxh> * We can generate the HTML documentation from this documentation comment by running cargo doc. This command runs the rustdoc tool distributed with Rust and puts the generated HTML documentation in the target/doc directory * To build and open documentation: <sxh bash; highlight: []>$ cargo doc --open</sxh> * With <nowiki>//!</nowiki> /*//*/we adds documentation to the item that contains the comments: <sxh rust; highlight: []>//! # My Crate //! //! `my_crate` is a collection of utilities to make performing certain //! calculations more convenient. /// Adds one to the number given. // --snip--</sxh> * Re-exporting pub use items in base library: <sxh rust; highlight: []>//! # Art //! //! A library for modeling artistic concepts. pub use self::kinds::PrimaryColor; pub use self::kinds::SecondaryColor; pub use self::utils::mix; pub mod kinds { // --snip-- } pub mod utils { // --snip-- }</sxh> * To login with an crates.io APIT token: <sxh bash; highlight: []>$ $ cargo login abcdefghijklmnopqrstuvwxyz012345</sxh> * To create a cargo workspace, we create a root folder with the cargo.toml file: <code>[workspace] members = [ "adder", "add_one", "add_two" ]</code> * To run a specific package in a workspace: <sxh bash; highlight: []>$ cargo run -p adder</sxh> * To run the tests for a particular workspace sub project only: <sxh bash; highlight: []>$ cargo test -p add-one</sxh> * Installing a package: <sxh bash; highlight: []>$ cargo install ripgrep Updating crates.io index Downloaded ripgrep v11.0.2 Downloaded 1 crate (243.3 KB) in 0.88s Installing ripgrep v11.0.2 --snip-- Compiling ripgrep v11.0.2 Finished release [optimized + debuginfo] target(s) in 3m 10s Installing ~/.cargo/bin/rg Installed package `ripgrep v11.0.2` (executable `rg`)</sxh> ===== 15 - Smart Pointers ===== * **link**: https://doc.rust-lang.org/book/ch15-00-smart-pointers.html * Smart pointers are usually implemented using structs. The characteristic that distinguishes a smart pointer from an ordinary struct is that smart pointers implement the **Deref** and **Drop** traits. * Using a Box<T> to Store Data on the Heap: <sxh rust; highlight: []>fn main() { let b = Box::new(5); println!("b = {}", b); }</sxh> * Implementing recursive types with Box: <sxh rust; highlight: []>enum List { Cons(i32, Box<List>), Nil, } use crate::List::{Cons, Nil}; fn main() { let list = Cons(1, Box::new(Cons(2, Box::new(Cons(3, Box::new(Nil)))))); }</sxh> * Following pointers with the dereference operator: <sxh rust; highlight: []>fn main() { let x = 5; let y = &x; assert_eq!(5, x); assert_eq!(5, *y); }</sxh> * Using Box<T> as a reference: <sxh rust; highlight: []>fn main() { let x = 5; let y = Box::new(x); assert_eq!(5, x); assert_eq!(5, *y); }</sxh> * Implementing Deref trait: <sxh rust; highlight: []>use std::ops::Deref; impl<T> Deref for MyBox<T> { type Target = T; fn deref(&self) -> &Self::Target { &self.0 } }</sxh> * Rust does deref coercion when it finds types and trait implementations in three cases: * From &T to &U when T: Deref<Target=U> * From &mut T to &mut U when T: DerefMut<Target=U> * From &mut T to &U when T: Deref<Target=U> * Drop trait implementation: <sxh rust; highlight: []>struct CustomSmartPointer { data: String, } impl Drop for CustomSmartPointer { fn drop(&mut self) { println!("Dropping CustomSmartPointer with data `{}`!", self.data); } } </sxh> * Dropping early with std::mem::drop: <sxh rust; highlight: []>fn main() { let c = CustomSmartPointer { data: String::from("some data"), }; println!("CustomSmartPointer created."); drop(c); println!("CustomSmartPointer dropped before the end of main."); }</sxh> * Example of using Rc<T>: <sxh rust; highlight: []>enum List { Cons(i32, Rc<List>), Nil, } use crate::List::{Cons, Nil}; use std::rc::Rc; fn main() { let a = Rc::new(Cons(5, Rc::new(Cons(10, Rc::new(Nil))))); let b = Cons(3, Rc::clone(&a)); let c = Cons(4, Rc::clone(&a)); }</sxh> * Counting references: <sxh rust; highlight: []>fn main() { let a = Rc::new(Cons(5, Rc::new(Cons(10, Rc::new(Nil))))); println!("count after creating a = {}", Rc::strong_count(&a)); let b = Cons(3, Rc::clone(&a)); println!("count after creating b = {}", Rc::strong_count(&a)); { let c = Cons(4, Rc::clone(&a)); println!("count after creating c = {}", Rc::strong_count(&a)); } println!("count after c goes out of scope = {}", Rc::strong_count(&a)); }</sxh> * Rc<T> enables multiple owners of the same data; Box<T> and RefCell<T> have single owners. * Box<T> allows immutable or mutable borrows checked at compile time; Rc<T> allows only immutable borrows checked at compile time; RefCell<T> allows immutable or mutable borrows checked at runtime. * Because RefCell<T> allows mutable borrows checked at runtime, you can mutate the value inside the RefCell<T> even when the RefCell<T> is immutable. * Some example with lifetime and trait bound: <sxh rust; highlight: []>pub trait Messenger { fn send(&self, msg: &str); } pub struct LimitTracker<'a, T: Messenger> { messenger: &'a T, value: usize, max: usize, } impl<'a, T> LimitTracker<'a, T> where T: Messenger, { pub fn new(messenger: &T, max: usize) -> LimitTracker<T> { LimitTracker { messenger, value: 0, max, } } pub fn set_value(&mut self, value: usize) { self.value = value; let percentage_of_max = self.value as f64 / self.max as f64; if percentage_of_max >= 1.0 { self.messenger.send("Error: You are over your quota!"); } else if percentage_of_max >= 0.9 { self.messenger .send("Urgent warning: You've used up over 90% of your quota!"); } else if percentage_of_max >= 0.75 { self.messenger .send("Warning: You've used up over 75% of your quota!"); } } } </sxh> * Example of using RefCell to allow for **interior mutability**: <sxh rust; highlight: []>#[cfg(test)] mod tests { use super::*; use std::cell::RefCell; struct MockMessenger { sent_messages: RefCell<Vec<String>>, } impl MockMessenger { fn new() -> MockMessenger { MockMessenger { sent_messages: RefCell::new(vec![]), } } } impl Messenger for MockMessenger { fn send(&self, message: &str) { self.sent_messages.borrow_mut().push(String::from(message)); } } #[test] fn it_sends_an_over_75_percent_warning_message() { // --snip-- assert_eq!(mock_messenger.sent_messages.borrow().len(), 1); } }</sxh> * Having Multiple Owners of Mutable Data by Combining Rc<T> and RefCell<T>: <sxh rust; highlight: []>#[derive(Debug)] enum List { Cons(Rc<RefCell<i32>>, Rc<List>), Nil, } use crate::List::{Cons, Nil}; use std::cell::RefCell; use std::rc::Rc; fn main() { let value = Rc::new(RefCell::new(5)); let a = Rc::new(Cons(Rc::clone(&value), Rc::new(Nil))); let b = Cons(Rc::new(RefCell::new(3)), Rc::clone(&a)); let c = Cons(Rc::new(RefCell::new(4)), Rc::clone(&a)); *value.borrow_mut() += 10; println!("a after = {:?}", a); println!("b after = {:?}", b); println!("c after = {:?}", c); }</sxh> * Creating a reference cycle (**this is a memory leak**): <sxh rust; highlight: []>fn main() { let a = Rc::new(Cons(5, RefCell::new(Rc::new(Nil)))); println!("a initial rc count = {}", Rc::strong_count(&a)); println!("a next item = {:?}", a.tail()); let b = Rc::new(Cons(10, RefCell::new(Rc::clone(&a)))); println!("a rc count after b creation = {}", Rc::strong_count(&a)); println!("b initial rc count = {}", Rc::strong_count(&b)); println!("b next item = {:?}", b.tail()); if let Some(link) = a.tail() { *link.borrow_mut() = Rc::clone(&b); } println!("b rc count after changing a = {}", Rc::strong_count(&b)); println!("a rc count after changing a = {}", Rc::strong_count(&a)); // Uncomment the next line to see that we have a cycle; // it will overflow the stack // println!("a next item = {:?}", a.tail()); }</sxh> * Preventing Reference Cycles: Turning an Rc<T> into a Weak<T>: <sxh rust; highlight: []>use std::cell::RefCell; use std::rc::{Rc, Weak}; #[derive(Debug)] struct Node { value: i32, parent: RefCell<Weak<Node>>, children: RefCell<Vec<Rc<Node>>>, } fn main() { let leaf = Rc::new(Node { value: 3, parent: RefCell::new(Weak::new()), children: RefCell::new(vec![]), }); println!("leaf parent = {:?}", leaf.parent.borrow().upgrade()); let branch = Rc::new(Node { value: 5, parent: RefCell::new(Weak::new()), children: RefCell::new(vec![Rc::clone(&leaf)]), }); *leaf.parent.borrow_mut() = Rc::downgrade(&branch); println!("leaf parent = {:?}", leaf.parent.borrow().upgrade()); }</sxh> * Visualizing strong and weak counts in the example below: <sxh rust; highlight: []>fn main() { let leaf = Rc::new(Node { value: 3, parent: RefCell::new(Weak::new()), children: RefCell::new(vec![]), }); println!( "leaf strong = {}, weak = {}", Rc::strong_count(&leaf), Rc::weak_count(&leaf), ); { let branch = Rc::new(Node { value: 5, parent: RefCell::new(Weak::new()), children: RefCell::new(vec![Rc::clone(&leaf)]), }); *leaf.parent.borrow_mut() = Rc::downgrade(&branch); println!( "branch strong = {}, weak = {}", Rc::strong_count(&branch), Rc::weak_count(&branch), ); println!( "leaf strong = {}, weak = {}", Rc::strong_count(&leaf), Rc::weak_count(&leaf), ); } println!("leaf parent = {:?}", leaf.parent.borrow().upgrade()); println!( "leaf strong = {}, weak = {}", Rc::strong_count(&leaf), Rc::weak_count(&leaf), ); }</sxh> ===== 16 - Fearless Concurrency ===== * **link**: https://doc.rust-lang.org/book/ch16-00-concurrency.html * Rust standard library only provides an implementation of 1:1 threading (ie. direct mapping to OS thread) instead of M:N threading model. * Example of spawning a thread: <sxh rust; highlight: []>use std::thread; use std::time::Duration; fn main() { let handle = thread::spawn(|| { for i in 1..10 { println!("hi number {} from the spawned thread!", i); thread::sleep(Duration::from_millis(1)); } }); for i in 1..5 { println!("hi number {} from the main thread!", i); thread::sleep(Duration::from_millis(1)); } handle.join().unwrap(); }</sxh> * Moving values into threads: <sxh rust; highlight: []>use std::thread; fn main() { let v = vec![1, 2, 3]; let handle = thread::spawn(move || { println!("Here's a vector: {:?}", v); }); handle.join().unwrap(); }</sxh> * We can create a new channel using the mpsc::channel function; mpsc stands for "multiple producer, single consumer": <sxh rust; highlight: []>use std::sync::mpsc; fn main() { let (tx, rx) = mpsc::channel(); }</sxh> * Sending transmitting end into a spawned thread: <sxh rust; highlight: []>use std::sync::mpsc; use std::thread; fn main() { let (tx, rx) = mpsc::channel(); thread::spawn(move || { let val = String::from("hi"); tx.send(val).unwrap(); }); let received = rx.recv().unwrap(); println!("Got: {}", received); }</sxh> * Sending multiple values: <sxh rust; highlight: []>use std::sync::mpsc; use std::thread; use std::time::Duration; fn main() { let (tx, rx) = mpsc::channel(); thread::spawn(move || { let vals = vec![ String::from("hi"), String::from("from"), String::from("the"), String::from("thread"), ]; for val in vals { tx.send(val).unwrap(); thread::sleep(Duration::from_secs(1)); } }); for received in rx { println!("Got: {}", received); } }</sxh> * Creating Multiple Producers by Cloning the Transmitter: <sxh rust; highlight: []> // --snip-- let (tx, rx) = mpsc::channel(); let tx1 = tx.clone(); thread::spawn(move || { let vals = vec![ String::from("hi"), String::from("from"), String::from("the"), String::from("thread"), ]; for val in vals { tx1.send(val).unwrap(); thread::sleep(Duration::from_secs(1)); } }); thread::spawn(move || { let vals = vec![ String::from("more"), String::from("messages"), String::from("for"), String::from("you"), ]; for val in vals { tx.send(val).unwrap(); thread::sleep(Duration::from_secs(1)); } }); for received in rx { println!("Got: {}", received); } // --snip--</sxh> * Simple example using Mutex<T> and atomic ref counting: <sxh rust; highlight: []>use std::sync::{Arc, Mutex}; use std::thread; fn main() { let counter = Arc::new(Mutex::new(0)); let mut handles = vec![]; for _ in 0..10 { let counter = Arc::clone(&counter); let handle = thread::spawn(move || { let mut num = counter.lock().unwrap(); *num += 1; }); handles.push(handle); } for handle in handles { handle.join().unwrap(); } println!("Result: {}", *counter.lock().unwrap()); }</sxh> * The **Send** marker trait indicates that ownership of values of the type implementing Send can be transferred between threads * The **Sync** marker trait indicates that it is safe for the type implementing Sync to be referenced from multiple threads. ===== 17 - Object Oriented Programming Features of Rust ===== * **link**: https://doc.rust-lang.org/book/ch17-00-oop.html * Simple encapsulation example: <sxh rust; highlight: []>pub struct AveragedCollection { list: Vec<i32>, average: f64, } impl AveragedCollection { pub fn add(&mut self, value: i32) { self.list.push(value); self.update_average(); } pub fn remove(&mut self) -> Option<i32> { let result = self.list.pop(); match result { Some(value) => { self.update_average(); Some(value) } None => None, } } pub fn average(&self) -> f64 { self.average } fn update_average(&mut self) { let total: i32 = self.list.iter().sum(); self.average = total as f64 / self.list.len() as f64; } } </sxh> * Simple usage of **Trait object**: <sxh rust; highlight: []>pub trait Draw { fn draw(&self); } pub struct Screen { pub components: Vec<Box<dyn Draw>>, } impl Screen { pub fn run(&self) { for component in self.components.iter() { component.draw(); } } }</sxh> ===== 18 - Patterns and Matching ===== * **link**: https://doc.rust-lang.org/book/ch18-00-patterns.html * Patterns can be used in: * **match** arms: <sxh rust; highlight: []>match VALUE { PATTERN => EXPRESSION, PATTERN => EXPRESSION, PATTERN => EXPRESSION, }</sxh> * **if let** conditions: <sxh rust; highlight: []>fn main() { let favorite_color: Option<&str> = None; let is_tuesday = false; let age: Result<u8, _> = "34".parse(); if let Some(color) = favorite_color { println!("Using your favorite color, {}, as the background", color); } else if is_tuesday { println!("Tuesday is green day!"); } else if let Ok(age) = age { if age > 30 { println!("Using purple as the background color"); } else { println!("Using orange as the background color"); } } else { println!("Using blue as the background color"); } }</sxh> * **while let** conditional loops: <sxh rust; highlight: []> let mut stack = Vec::new(); stack.push(1); stack.push(2); stack.push(3); while let Some(top) = stack.pop() { println!("{}", top); }</sxh> * for loops: <sxh rust; highlight: []> let v = vec!['a', 'b', 'c']; for (index, value) in v.iter().enumerate() { println!("{} is at index {}", value, index); }</sxh> * let statements: <sxh rust; highlight: []>let (x, y, z) = (1, 2, 3);</sxh> * function parameters: <sxh rust; highlight: []>fn print_coordinates(&(x, y): &(i32, i32)) { println!("Current location: ({}, {})", x, y); } fn main() { let point = (3, 5); print_coordinates(&point); }</sxh> * Patterns come in two forms: refutable and irrefutable. Patterns that will match for any possible value passed are irrefutable. * Function parameters, let statements, and for loops can only accept irrefutable Patterns * Pattern matchin literals example: <sxh rust; highlight: []> let x = 1; match x { 1 => println!("one"), 2 => println!("two"), 3 => println!("three"), _ => println!("anything"), }</sxh> * Matching multiple patterns: <sxh rust; highlight: []> let x = 1; match x { 1 | 2 => println!("one or two"), 3 => println!("three"), _ => println!("anything"), }</sxh> * Matching a range of values: <sxh rust; highlight: []> let x = 5; match x { 1..=5 => println!("one through five"), _ => println!("something else"), }</sxh> * Destructuring a struct: <sxh rust; highlight: []>struct Point { x: i32, y: i32, } fn main() { let p = Point { x: 0, y: 7 }; let Point { x: a, y: b } = p; assert_eq!(0, a); assert_eq!(7, b); }</sxh> * destructuring shortcut when we want to use the same names for the variables: <sxh rust; highlight: []>struct Point { x: i32, y: i32, } fn main() { let p = Point { x: 0, y: 7 }; let Point { x, y } = p; assert_eq!(0, x); assert_eq!(7, y); }</sxh> * Or with some literals: <sxh rust; highlight: []>fn main() { let p = Point { x: 0, y: 7 }; match p { Point { x, y: 0 } => println!("On the x axis at {}", x), Point { x: 0, y } => println!("On the y axis at {}", y), Point { x, y } => println!("On neither axis: ({}, {})", x, y), } }</sxh> * Example destructuring enum: <sxh rust; highlight: []>enum Message { Quit, Move { x: i32, y: i32 }, Write(String), ChangeColor(i32, i32, i32), } fn main() { let msg = Message::ChangeColor(0, 160, 255); match msg { Message::Quit => { println!("The Quit variant has no data to destructure.") } Message::Move { x, y } => { println!( "Move in the x direction {} and in the y direction {}", x, y ); } Message::Write(text) => println!("Text message: {}", text), Message::ChangeColor(r, g, b) => println!( "Change the color to red {}, green {}, and blue {}", r, g, b ), } }</sxh> * Destructuring nested struct and enums: <sxh rust; highlight: []>enum Color { Rgb(i32, i32, i32), Hsv(i32, i32, i32), } enum Message { Quit, Move { x: i32, y: i32 }, Write(String), ChangeColor(Color), } fn main() { let msg = Message::ChangeColor(Color::Hsv(0, 160, 255)); match msg { Message::ChangeColor(Color::Rgb(r, g, b)) => println!( "Change the color to red {}, green {}, and blue {}", r, g, b ), Message::ChangeColor(Color::Hsv(h, s, v)) => println!( "Change the color to hue {}, saturation {}, and value {}", h, s, v ), _ => (), } }</sxh> * Destructuring struct and tuples: <sxh rust; highlight: []>let ((feet, inches), Point { x, y }) = ((3, 10), Point { x: 3, y: -10 });</sxh> * Ignoring values in pattern: * using the _ pattern, * using the _ pattern within another pattern, * using a name that starts with an underscore, * or using .. to ignore remaining parts of a value. * Example of _ pattern within another pattern: <sxh rust; highlight: []> let mut setting_value = Some(5); let new_setting_value = Some(10); match (setting_value, new_setting_value) { (Some(_), Some(_)) => { println!("Can't overwrite an existing customized value"); } _ => { setting_value = new_setting_value; } } println!("setting is {:?}", setting_value);</sxh> * ignoring remaining part of a pattern: <sxh rust; highlight: []> struct Point { x: i32, y: i32, z: i32, } let origin = Point { x: 0, y: 0, z: 0 }; match origin { Point { x, .. } => println!("x is {}", x), }</sxh> * The syntax .. will expand to as many values as it needs to be: <sxh rust; highlight: []>fn main() { let numbers = (2, 4, 8, 16, 32); match numbers { (first, .., last) => { println!("Some numbers: {}, {}", first, last); } } }</sxh> * Extra Conditionals with Match Guards: <sxh rust; highlight: []> let num = Some(4); match num { Some(x) if x < 5 => println!("less than five: {}", x), Some(x) => println!("{}", x), None => (), }</sxh> * You can also use the or operator | in a match guard to specify multiple patterns; the match guard condition will apply to **all the patterns** (if not using parentheses): <sxh rust; highlight: []> let x = 4; let y = false; match x { 4 | 5 | 6 if y => println!("yes"), _ => println!("no"), }</sxh> * Otherwise could write: <sxh rust; highlight: []> let x = 4; let y = false; match x { 4 | 5 | (6 if y) => println!("yes"), _ => println!("no"), }</sxh> * The at operator (@) lets us create a variable that holds a value at the same time we’re testing that value to see whether it matches a pattern: <sxh rust; highlight: []> enum Message { Hello { id: i32 }, } let msg = Message::Hello { id: 5 }; match msg { Message::Hello { id: id_variable @ 3..=7, } => println!("Found an id in range: {}", id_variable), Message::Hello { id: 10..=12 } => { println!("Found an id in another range") } Message::Hello { id } => println!("Found some other id: {}", id), }</sxh> ===== 19 - Advanced Features ===== * **link**: https://doc.rust-lang.org/book/ch19-00-advanced-features.html * To switch to unsafe Rust, use the **unsafe** keyword and then start a new block that holds the unsafe code. You can take five actions in unsafe Rust, called **unsafe superpowers**: * Dereference a raw pointer * Call an unsafe function or method * Access or modify a mutable static variable * Implement an unsafe trait * Access fields of unions * Unsafe Rust has two new types called raw pointers that are similar to references. As with references, raw pointers can be immutable or mutable and are written as ''*const T'' and ''*mut T'', respectively. * Creating raw pointers example (unsafe only needed to dereference them, not to create them): <sxh rust; highlight: []> let mut num = 5; let r1 = &num as *const i32; let r2 = &mut num as *mut i32;</sxh> * Calling an unsafe function or method: <sxh rust; highlight: []> unsafe fn dangerous() {} unsafe { dangerous(); }</sxh> * Example of safe encapsulation of unsafe code: <sxh rust; highlight: []>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); unsafe { ( slice::from_raw_parts_mut(ptr, mid), slice::from_raw_parts_mut(ptr.add(mid), len - mid), ) } }</sxh> * Sometimes, your Rust code might need to interact with code written in another language. For this, Rust has a keyword, **extern**, that facilitates the creation and use of a Foreign Function Interface (FFI): <sxh rust; highlight: []>extern "C" { fn abs(input: i32) -> i32; } fn main() { unsafe { println!("Absolute value of -3 according to C: {}", abs(-3)); } }</sxh> * Calling Rust Functions from Other Languages: <sxh rust; highlight: []>#[no_mangle] pub extern "C" fn call_from_c() { println!("Just called a Rust function from C!"); }</sxh> * Accessing and modifying mutable static variables is unsafe: <sxh rust; highlight: []>static mut COUNTER: u32 = 0; fn add_to_count(inc: u32) { unsafe { COUNTER += inc; } } fn main() { add_to_count(3); unsafe { println!("COUNTER: {}", COUNTER); } }</sxh> * Implementing unsafe traits: <sxh rust; highlight: []>unsafe trait Foo { // methods go here } unsafe impl Foo for i32 { // method implementations go here } fn main() {}</sxh> * An **union** is similar to a struct, but only one declared field is used in a particular instance at one time. Unions are primarily used to interface with unions in C code. * Example of placeholder/associated type in a trait: <sxh rust; highlight: []>pub trait Iterator { type Item; fn next(&mut self) -> Option<Self::Item>; }</sxh> * Default Generic Type Parameters and Operator Overloading: <sxh rust; highlight: []>use std::ops::Add; #[derive(Debug, Copy, Clone, PartialEq)] struct Point { x: i32, y: i32, } impl Add for Point { type Output = Point; fn add(self, other: Point) -> Point { Point { x: self.x + other.x, y: self.y + other.y, } } } fn main() { assert_eq!( Point { x: 1, y: 0 } + Point { x: 2, y: 3 }, Point { x: 3, y: 3 } ); }</sxh> * => The source Add trait is defined as (using a default generic value here): <sxh rust; highlight: []>trait Add<Rhs=Self> { type Output; fn add(self, rhs: Rhs) -> Self::Output; }</sxh> * Or we can specify the Rhs class: <sxh rust; highlight: []>use std::ops::Add; struct Millimeters(u32); struct Meters(u32); impl Add<Meters> for Millimeters { type Output = Millimeters; fn add(self, other: Meters) -> Millimeters { Millimeters(self.0 + (other.0 * 1000)) } }</sxh> * Fully Qualified Syntax for Disambiguation: Calling Methods with the Same Name: <sxh rust; highlight: []>trait Pilot { fn fly(&self); } trait Wizard { fn fly(&self); } struct Human; impl Pilot for Human { fn fly(&self) { println!("This is your captain speaking."); } } impl Wizard for Human { fn fly(&self) { println!("Up!"); } } impl Human { fn fly(&self) { println!("*waving arms furiously*"); } }</sxh> * When we call fly on an instance of Human, **the compiler defaults to calling the method that is directly implemented on the type**. * To call the trait functions instead we would use: <sxh rust; highlight: []>fn main() { let person = Human; Pilot::fly(&person); Wizard::fly(&person); person.fly(); }</sxh> * We could also write ''Human::fly(&person)'' * Disambiguation for trait functions: <sxh rust; highlight: []>trait Animal { fn baby_name() -> String; } struct Dog; impl Dog { fn baby_name() -> String { String::from("Spot") } } impl Animal for Dog { fn baby_name() -> String { String::from("puppy") } } fn main() { println!("A baby dog is called a {}", <Dog as Animal>::baby_name()); }</sxh> * In general, fully qualified syntax is defined as follows: ''<Type as Trait>::function(receiver_if_method, next_arg, ...);'' * Sometimes, you might need one trait to use another trait’s functionality. In this case, you need to rely on the dependent trait also being implemented. The trait you rely on is a **supertrait** of the trait you’re implementing: <sxh rust; highlight: []>use std::fmt; trait OutlinePrint: fmt::Display { fn outline_print(&self) { let output = self.to_string(); let len = output.len(); println!("{}", "*".repeat(len + 4)); println!("*{}*", " ".repeat(len + 2)); println!("* {} *", output); println!("*{}*", " ".repeat(len + 2)); println!("{}", "*".repeat(len + 4)); } }</sxh> * Using the Newtype Pattern to Implement External Traits on External Types: <sxh rust; highlight: []>use std::fmt; struct Wrapper(Vec<String>); impl fmt::Display for Wrapper { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "[{}]", self.0.join(", ")) } } fn main() { let w = Wrapper(vec![String::from("hello"), String::from("world")]); println!("w = {}", w); }</sxh> * => Could consider implement Deref on the Wrapper type above to access all functions from Vec<T> directly. * Creating Type Synonyms with Type Aliases: <sxh rust; highlight: []> type Kilometers = i32; let x: i32 = 5; let y: Kilometers = 5; println!("x + y = {}", x + y); type Thunk = Box<dyn Fn() + Send + 'static>; let f: Thunk = Box::new(|| println!("hi")); fn takes_long_type(f: Thunk) { // --snip-- } fn returns_long_type() -> Thunk { // --snip-- }</sxh> * Can also use generics: <sxh rust; highlight: []>type Result<T> = std::result::Result<T, std::io::Error>;</sxh> * The Never Type that Never Returns: <sxh rust; highlight: []>fn bar() -> ! { // --snip-- }</sxh> * By default, generic functions will work only on types that have a known size at compile time. However, you can use the following special syntax to relax this restriction: <sxh rust; highlight: []>fn generic<T: ?Sized>(t: &T) { // --snip-- }</sxh> * A trait bound on **?Sized** means “T may or may not be Sized” and this notation overrides the default that generic types must have a known size at compile time. The **?Trait** syntax with this meaning is only available for Sized, not any other traits. * Function Pointers: <sxh rust; highlight: []>fn add_one(x: i32) -> i32 { x + 1 } fn do_twice(f: fn(i32) -> i32, arg: i32) -> i32 { f(arg) + f(arg) } fn main() { let answer = do_twice(add_one, 5); println!("The answer is: {}", answer); }</sxh> * Like above, this will also work: <sxh rust; highlight: []> enum Status { Value(u32), Stop, } let list_of_statuses: Vec<Status> = (0u32..20).map(Status::Value).collect();</sxh> * To be able to return a closure we have to use a Box: <sxh rust; highlight: []>fn returns_closure() -> Box<dyn Fn(i32) -> i32> { Box::new(|x| x + 1) }</sxh> * Example of how to build a **declarative macro**: <sxh rust; highlight: []>#[macro_export] macro_rules! vec { ( $( $x:expr ),* ) => { { let mut temp_vec = Vec::new(); $( temp_vec.push($x); )* temp_vec } }; }</sxh> * When calling ''vec![1,2,3]'', the $x variable will collect the 1, 2 and 3 values, and this code will be generated: <sxh rust; highlight: []>{ let mut temp_vec = Vec::new(); temp_vec.push(1); temp_vec.push(2); temp_vec.push(3); temp_vec }</sxh> * The second form of macros is **procedural macros** * The three kinds of procedural macros (custom derive, attribute-like, and function-like) all work in a similar fashion. * When creating procedural macros, the definitions must reside in their own crate with a special crate type, using this kind of toml file: <code>[lib] proc-macro = true [dependencies] syn = "1.0" quote = "1.0"</code> * Implementation of procedural macro: <sxh rust; highlight: []>extern crate proc_macro; use proc_macro::TokenStream; use quote::quote; use syn; #[proc_macro_derive(HelloMacro)] pub fn hello_macro_derive(input: TokenStream) -> TokenStream { // Construct a representation of Rust code as a syntax tree // that we can manipulate let ast = syn::parse(input).unwrap(); // Build the trait implementation impl_hello_macro(&ast) } fn impl_hello_macro(ast: &syn::DeriveInput) -> TokenStream { let name = &ast.ident; let gen = quote! { impl HelloMacro for #name { fn hello_macro() { println!("Hello, Macro! My name is {}!", stringify!(#name)); } } }; gen.into() }</sxh> * note: can specify path to dependency in a cargo toml file with: <code>hello_macro = { path = "../hello_macro" } hello_macro_derive = { path = "../hello_macro/hello_macro_derive" }</code> ===== 20 - Final Project: Building a Multithreaded Web Server ===== * **link**: https://doc.rust-lang.org/book/ch20-00-final-project-a-web-server.html * Code of the final project: <sxh rust; highlight: []>// file src/bin/main.rs: use hello::ThreadPool; use std::fs; use std::io::prelude::*; use std::net::TcpListener; use std::net::TcpStream; use std::thread; use std::time::Duration; fn main() { let listener = TcpListener::bind("127.0.0.1:7878").unwrap(); let pool = ThreadPool::new(4); for stream in listener.incoming() { let stream = stream.unwrap(); pool.execute(|| { handle_connection(stream); }); } println!("Shutting down."); } fn handle_connection(mut stream: TcpStream) { let mut buffer = [0; 1024]; stream.read(&mut buffer).unwrap(); let get = b"GET / HTTP/1.1\r\n"; let sleep = b"GET /sleep HTTP/1.1\r\n"; let (status_line, filename) = if buffer.starts_with(get) { ("HTTP/1.1 200 OK", "hello.html") } else if buffer.starts_with(sleep) { thread::sleep(Duration::from_secs(5)); ("HTTP/1.1 200 OK", "hello.html") } else { ("HTTP/1.1 404 NOT FOUND", "404.html") }; let contents = fs::read_to_string(filename).unwrap(); let response = format!( "{}\r\nContent-Length: {}\r\n\r\n{}", status_line, contents.len(), contents ); stream.write(response.as_bytes()).unwrap(); stream.flush().unwrap(); } // file src/lib.rs: use std::sync::mpsc; use std::sync::Arc; use std::sync::Mutex; use std::thread; pub struct ThreadPool { workers: Vec<Worker>, sender: mpsc::Sender<Message>, } type Job = Box<dyn FnOnce() + Send + 'static>; enum Message { NewJob(Job), Terminate, } impl ThreadPool { /// Create a new ThreadPool. /// /// The size is the number of threads in the pool. /// /// # Panics /// /// The `new` function will panic if the size is zero. pub fn new(size: usize) -> ThreadPool { assert!(size > 0); let (sender, receiver) = mpsc::channel(); let receiver = Arc::new(Mutex::new(receiver)); let mut workers = Vec::with_capacity(size); for id in 0..size { workers.push(Worker::new(id, Arc::clone(&receiver))); } ThreadPool { workers, sender } } pub fn execute<F>(&self, f: F) where F: FnOnce() + Send + 'static, { let job = Box::new(f); self.sender.send(Message::NewJob(job)).unwrap(); } } impl Drop for ThreadPool { fn drop(&mut self) { println!("Sending terminate message to all workers."); for _ in &self.workers { self.sender.send(Message::Terminate).unwrap(); } println!("Shutting down all workers."); for worker in &mut self.workers { println!("Shutting down worker {}", worker.id); if let Some(thread) = worker.thread.take() { thread.join().unwrap(); } } } } struct Worker { id: usize, thread: Option<thread::JoinHandle<()>>, } impl Worker { fn new(id: usize, receiver: Arc<Mutex<mpsc::Receiver<Message>>>) -> Worker { let thread = thread::spawn(move || loop { let message = receiver.lock().unwrap().recv().unwrap(); match message { Message::NewJob(job) => { println!("Worker {} got a job; executing.", id); job(); } Message::Terminate => { println!("Worker {} was told to terminate.", id); break; } } }); Worker { id, thread: Some(thread), } } } </sxh> public/books/rust_programming_language/intro.txt Last modified: 2022/03/03 13:29by 127.0.0.1