public:books:rust_programming_language:intro

The Rust Programming Language

  • 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:
    let a = [1, 2, 3, 4, 5];
    
    let slice = &a[1..3];
  • Struct fields init shorthand:
    fn build_user(email: String, username: String) -> User {
      User {
          email,
          username,
          active: true,
          sign_in_count: 1,
      }
    }
  • Struct update syntax:
        let user2 = User {
          email: String::from("another@example.com"),
          ..user1
      };
  • Tuple structs:
        struct Color(i32, i32, i32);
      struct Point(i32, i32, i32);
    
      let black = Color(0, 0, 0);
      let origin = Point(0, 0, 0);
  • Unit-like structs:
        struct AlwaysEqual;
    
      let subject = AlwaysEqual;
  • Usage of dbg! macro:
    #[derive(Debug)]
    struct Rectangle {
      width: u32,
      height: u32,
    }
    
    fn main() {
      let scale = 2;
      let rect1 = Rectangle {
          width: dbg!(30 * scale),
          height: 50,
      };
    
      dbg!(&rect1);
    }
  • 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.
  • We can put any kind of data in enums:
    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),
    }
  • We can provide impl on enums.
  • The option enum to use as a replacement for the “null” concept:
    enum Option<T> {
      None,
      Some(T),
    }
  • Catch-all Patterns and the underscore Placeholder:
        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() {}
  • usage of if let an syntaxtic sugar for match:
        let mut count = 0;
      if let Coin::Quarter(state) = coin {
          println!("State quarter from {:?}!", state);
      } else {
          count += 1;
      }
  • To bring a function into the scope it is idiomatic to bring the parent module of that function:
    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();
    }
  • To bring a struct, enums, etc it is idiomatic to bring the full path (expect if there is a conflict):
    use std::collections::HashMap;
    
    fn main() {
      let mut map = HashMap::new();
      map.insert(1, 2);
    }
  • We can use as to resolve a name conflict:
    use std::fmt::Result;
    use std::io::Result as IoResult;
    
    fn function1() -> Result {
      // --snip--
    }
    
    fn function2() -> IoResult<()> {
      // --snip--
    }
  • We can re-export use paths with pub:
    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();
    }
  • Can use nested pathswith use:
    use std::{cmp::Ordering, io};
    use std::io::{self, Write};
  • Usage of glob operator to bring all public items:
    use std::collections::*;
  • We can move modules in their own files, and then specify that we use them with:
    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();
    }
  • Vector usage:
    // 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);
    
    /
  • When the vector gets dropped, all of its contents are also dropped
  • get the values in a vector:
        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."),
      }
  • Using an enum to store multiple types:
        enum SpreadsheetCell {
          Int(i32),
          Float(f64),
          Text(String),
      }
    
      let row = vec![
          SpreadsheetCell::Int(3),
          SpreadsheetCell::Text(String::from("blue")),
          SpreadsheetCell::Float(10.12),
      ];
  • 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:
        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);
  • To disable unwinding at release for instance, we can add in Cargo.toml:
    [profile.release]
    panic = 'abort'
  • Manually calling panic!:
    fn main() {
      panic!("crash and burn");
    }
  • 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:
    enum Result<T, E> {
      Ok(T),
      Err(E),
    }
  • More advanced matching with inner matches:
    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)
              }
          },
      };
    }
  • Alternate code with closure:
    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);
          }
      });
    }
  • Propagating errors:
    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),
      }
    }
  • Question mark shortcut to propagate errors:
    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)
    }
  • haéndling errors directly in main:
    use std::error::Error;
    use std::fs::File;
    
    fn main() -> Result<(), Box<dyn Error>> {
      let f = File::open("hello.txt")?;
    
      Ok(())
    }
  • Struct using generics:
    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,
    }
    
    /
  • Enums using generics:
    enum Option<T> {
      Some(T),
      None,
    }
    
    enum Result<T, E> {
      Ok(T),
      Err(E),
    }
  • Example of generic method in generic struct impl:
    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,
          }
      }
    }
  • Rust performs monomorphization of the generics ⇒ no performance cost at runtime.
  • Declaring a trait:
    pub trait Summary {
      fn summarize(&self) -> String;
    }
  • Implementing a trait on a type:
    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)
      }
    }
  • Default method implementation in trait:
    pub trait Summary {
      fn summarize(&self) -> String {
          String::from("(Read more...)")
      }
    }
    
    impl Summary for NewsArticle {}
  • Using trait as parameter:
    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());
    }
    /
  • Multiple Trait Bounds with the + Syntax:
    pub fn notify(item: &(impl Summary + Display)) {}
    
    // or
    pub fn notify<T: Summary + Display>(item: &T) {}
    
    /
  • Trait Bounds with where Clauses:
    fn some_function<T, U>(t: &T, u: &U) -> i32
      where T: Display + Clone,
            U: Clone + Debug
    {}
  • Returning Types that Implement Traits:
    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,
      }
    }
  • ⇒ However, you can only use impl Trait if you’re returning a single type
  • Conditional method implementation with traits:
    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);
          }
      }
    }
  • 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:
    impl<T: Display> ToString for T {
      // --snip--
    }
  • lifetime annotation syntax:
    &i32        // a reference
    &'a i32     // a reference with an explicit lifetime
    &'a mut i32 // a mutable reference with an explicit lifetime
    /
  • Lifetime Annotations in Function Signatures:
    fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
      if x.len() > y.len() {
          x
      } else {
          y
      }
    }
  • Lifetime Annotations in Struct Definitions:
    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,
      };
    }
  • Can have multiple lifetimes in function declaration:
    fn foo<'a, 'b>(x: &'a i32, y: &'b i32);
  • The patterns programmed into Rust’s analysis of references are called the lifetime elision rules
  • Static lifetime:
    let s: &'static str = "I have a static lifetime.";
  • 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:
    impl<'a> ImportantExcerpt<'a> {
      fn announce_and_return_part(&self, announcement: &str) -> &str {
          println!("Attention please: {}", announcement);
          self.part
      }
    }
  • Generic Type Parameters, Trait Bounds, and Lifetimes Together:
    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
      }
    }
  • A simple test function in a test module:
    #[cfg(test)]
    mod tests {
      #[test]
      fn it_works() {
          assert_eq!(2 + 2, 4);
      }
    }
  • To run the tests we use the command:
    $ cargo test
  • Tests can use panic!:
    #[cfg(test)]
    mod tests {
      #[test]
      fn exploration() {
          assert_eq!(2 + 2, 4);
      }
    
      #[test]
      fn another() {
          panic!("Make this test fail");
      }
    }
  • Tests can use assert!:
    #[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));
      }
    }
  • Other available test macros: assert_eq! and assert_ne!
  • Custom failure messages:
        #[test]
      fn greeting_contains_name() {
          let result = greeting("Carol");
          assert!(
              result.contains("Carol"),
              "Greeting did not contain name, value was `{}`",
              result
          );
      }
  • Can check for panics with should_panic:
    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);
      }
    }
    
  • Can use Result<T, E> as test return value:
    #[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"))
          }
      }
    }
  • Note: can’t use the #[should_panic] annotation on tests that use Result<T, E>.
  • To run threads consecutively:
    $ cargo test -- --test-threads=1
  • To show function outputs from tests:
    $ cargo test -- --show-output
  • Select tests by name:
    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));
      }
    }
  • then we can specify 'partial names':
    $ 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
    
    
  • Can ignore expensive test by default with:
    #[test]
    fn it_works() {
      assert_eq!(2 + 2, 4);
    }
    
    #[test]
    #[ignore]
    fn expensive_test() {
      // code that takes an hour to run
    }
  • And then run them with:
    $ cargo test -- --ignored
  • And we can run all tests (ignored or not) with:
    cargo test -- --include-ignored
  • 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:
    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));
      }
    }
  • 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:
    use adder;
    
    #[test]
    fn it_adds_two() {
      assert_eq!(4, adder::add_two(2));
    }
  • To run all the tests in a particular integration test file:
    $ 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)
    
  • 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.
  • Test-driven development (TDD) process: This software development technique follows these steps:
    1. Write a test that fails and run it to make sure it fails for the reason you expect.
    2. Write or modify just enough code to make the new test pass.
    3. Refactor the code you just added or changed and make sure the tests continue to pass.
    4. Repeat from step 1!
  • Minimal skeleton for minigrep app:
    // 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
    }
    
  • Checking for an environment variable:
    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,
          })
      }
    }
  • To print messages on stderr:
    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);
      }
    }
  • Sleeping on a thread:
    use std::thread;
    use std::time::Duration;
    
    fn simulated_expensive_calculation(intensity: u32) -> u32 {
      println!("calculating slowly...");
      thread::sleep(Duration::from_secs(2));
      intensity
    }
  • Example of closure definition:
    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)
              );
          }
      }
    }
  • Optional elements in closure syntax:
    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  ;
  • 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:
    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
              }
          }
      }
    }
  • Then we could use the Cacher struct above as follow for instance:
        let mut expensive_result = Cacher::new(|num| {
          println!("calculating slowly...");
          thread::sleep(Duration::from_secs(2));
          num
      });
    
  • Example closure capturing environmnet:
    fn main() {
      let x = 4;
    
      let equal_to_x = |z| z == x;
    
      let y = 4;
    
      assert!(equal_to_x(y));
    }
  • 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:
      // 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));
    }
  • Creating an iterator:
        let v1 = vec![1, 2, 3];
    
      let v1_iter = v1.iter();
    
      for val in v1_iter {
          println!("Got: {}", val);
      }
  • Iterator trait:
    pub trait Iterator {
      type Item;
    
      fn next(&mut self) -> Option<Self::Item>;
    
      // methods with default implementations elided
    }
  • Chaining iterators:
        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]);
  • the filter iterator adaptor example:
    #[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")
                  },
              ]
          );
      }
    }
  • Implementing an iterator:
    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
          }
      }
    }
  • Example of advanced iterators usage:
        #[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);
      }
  • Using iterators to get env args:
    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,
          })
      }
    }
  • Usint iterators to search in minigrep app:
    pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
      contents
          .lines()
          .filter(|line| line.contains(query))
          .collect()
    }
  • Cargo documentation: https://doc.rust-lang.org/cargo/
  • Different release profiles:
    $ cargo build
      Finished dev [unoptimized + debuginfo] target(s) in 0.0s
    $ cargo build --release
      Finished release [optimized] target(s) in 0.0s
  • Can set profile settings in cargo.toml file:
    [profile.dev]
    opt-level = 0
    
    [profile.release]
    opt-level = 3
  • Documentation comments use three slashes, ///, instead of two and support Markdown notation for formatting the text. Place documentation comments just before the item they’re documenting:
    /// 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
    }
  • 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:
    $ cargo doc --open
  • With //!we adds documentation to the item that contains the comments:
    //! # My Crate
    //!
    //! `my_crate` is a collection of utilities to make performing certain
    //! calculations more convenient.
    
    /// Adds one to the number given.
    // --snip--
  • Re-exporting pub use items in base library:
    //! # 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--
    }
  • To login with an crates.io APIT token:
    $ $ cargo login abcdefghijklmnopqrstuvwxyz012345
  • To create a cargo workspace, we create a root folder with the cargo.toml file:
    [workspace]
    
    members = [
      "adder",
      "add_one",
      "add_two"
    ]
  • To run a specific package in a workspace:
    $ cargo run -p adder
  • To run the tests for a particular workspace sub project only:
    $ cargo test -p add-one
  • Installing a package:
    $ 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`)
  • 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:
    fn main() {
      let b = Box::new(5);
      println!("b = {}", b);
    }
  • Implementing recursive types with Box:
    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))))));
    }
  • Following pointers with the dereference operator:
    fn main() {
      let x = 5;
      let y = &x;
    
      assert_eq!(5, x);
      assert_eq!(5, *y);
    }
  • Using Box<T> as a reference:
    fn main() {
      let x = 5;
      let y = Box::new(x);
    
      assert_eq!(5, x);
      assert_eq!(5, *y);
    }
  • Implementing Deref trait:
    use std::ops::Deref;
    
    impl<T> Deref for MyBox<T> {
      type Target = T;
    
      fn deref(&self) -> &Self::Target {
          &self.0
      }
    }
  • 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:
    struct CustomSmartPointer {
      data: String,
    }
    
    impl Drop for CustomSmartPointer {
      fn drop(&mut self) {
          println!("Dropping CustomSmartPointer with data `{}`!", self.data);
      }
    }
    
  • Dropping early with std::mem::drop:
    fn main() {
      let c = CustomSmartPointer {
          data: String::from("some data"),
      };
      println!("CustomSmartPointer created.");
      drop(c);
      println!("CustomSmartPointer dropped before the end of main.");
    }
  • Example of using Rc<T>:
    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));
    }
  • Counting references:
    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));
    }
  • 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:
    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!");
          }
      }
    }
    
  • Example of using RefCell to allow for interior mutability:
    #[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);
      }
    }
  • Having Multiple Owners of Mutable Data by Combining Rc<T> and RefCell<T>:
    #[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);
    }
  • Creating a reference cycle (this is a memory leak):
    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());
    }
  • Preventing Reference Cycles: Turning an Rc<T> into a Weak<T>:
    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());
    }
  • Visualizing strong and weak counts in the example below:
    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),
      );
    }
  • 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:
    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();
    }
  • Moving values into threads:
    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();
    }
  • We can create a new channel using the mpsc::channel function; mpsc stands for “multiple producer, single consumer”:
    use std::sync::mpsc;
    
    fn main() {
      let (tx, rx) = mpsc::channel();
    }
  • Sending transmitting end into a spawned thread:
    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);
    }
  • Sending multiple values:
    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);
      }
    }
  • Creating Multiple Producers by Cloning the Transmitter:
        // --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--
  • Simple example using Mutex<T> and atomic ref counting:
    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());
    }
  • 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.
  • Simple encapsulation example:
    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;
      }
    }
    
  • Simple usage of Trait object:
    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();
          }
      }
    }
  • Patterns can be used in:
    • match arms:
      match VALUE {
        PATTERN => EXPRESSION,
        PATTERN => EXPRESSION,
        PATTERN => EXPRESSION,
      }
    • if let conditions:
      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");
        }
      }
    • while let conditional loops:
          let mut stack = Vec::new();
      
        stack.push(1);
        stack.push(2);
        stack.push(3);
      
        while let Some(top) = stack.pop() {
            println!("{}", top);
        }
    • for loops:
          let v = vec!['a', 'b', 'c'];
      
        for (index, value) in v.iter().enumerate() {
            println!("{} is at index {}", value, index);
        }
    • let statements:
      let (x, y, z) = (1, 2, 3);
    • function parameters:
      fn print_coordinates(&(x, y): &(i32, i32)) {
        println!("Current location: ({}, {})", x, y);
      }
      
      fn main() {
        let point = (3, 5);
        print_coordinates(&point);
      }
  • 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:
        let x = 1;
    
      match x {
          1 => println!("one"),
          2 => println!("two"),
          3 => println!("three"),
          _ => println!("anything"),
      }
  • Matching multiple patterns:
        let x = 1;
    
      match x {
          1 | 2 => println!("one or two"),
          3 => println!("three"),
          _ => println!("anything"),
      }
  • Matching a range of values:
        let x = 5;
    
      match x {
          1..=5 => println!("one through five"),
          _ => println!("something else"),
      }
  • Destructuring a struct:
    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);
    }
  • destructuring shortcut when we want to use the same names for the variables:
    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);
    }
  • Or with some literals:
    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),
      }
    }
  • Example destructuring enum:
    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
          ),
      }
    }
  • Destructuring nested struct and enums:
    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
          ),
          _ => (),
      }
    }
  • Destructuring struct and tuples:
    let ((feet, inches), Point { x, y }) = ((3, 10), Point { x: 3, y: -10 });
  • 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:
        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);
  • ignoring remaining part of a pattern:
        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),
      }
  • The syntax .. will expand to as many values as it needs to be:
    fn main() {
      let numbers = (2, 4, 8, 16, 32);
    
      match numbers {
          (first, .., last) => {
              println!("Some numbers: {}, {}", first, last);
          }
      }
    }
  • Extra Conditionals with Match Guards:
        let num = Some(4);
    
      match num {
          Some(x) if x < 5 => println!("less than five: {}", x),
          Some(x) => println!("{}", x),
          None => (),
      }
  • 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):
        let x = 4;
      let y = false;
    
      match x {
          4 | 5 | 6 if y => println!("yes"),
          _ => println!("no"),
      }
  • Otherwise could write:
        let x = 4;
      let y = false;
    
      match x {
          4 | 5 | (6 if y) => println!("yes"),
          _ => println!("no"),
      }
  • 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:
        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),
      }
  • 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):
        let mut num = 5;
    
      let r1 = &num as *const i32;
      let r2 = &mut num as *mut i32;
  • Calling an unsafe function or method:
        unsafe fn dangerous() {}
    
      unsafe {
          dangerous();
      }
  • Example of safe encapsulation of unsafe code:
    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),
            )
        }
    }
  • 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):
    extern "C" {
        fn abs(input: i32) -> i32;
    }
    
    fn main() {
        unsafe {
            println!("Absolute value of -3 according to C: {}", abs(-3));
        }
    }
  • Calling Rust Functions from Other Languages:
    #[no_mangle]
    pub extern "C" fn call_from_c() {
        println!("Just called a Rust function from C!");
    }
  • Accessing and modifying mutable static variables is unsafe:
    static mut COUNTER: u32 = 0;
    
    fn add_to_count(inc: u32) {
        unsafe {
            COUNTER += inc;
        }
    }
    
    fn main() {
        add_to_count(3);
    
        unsafe {
            println!("COUNTER: {}", COUNTER);
        }
    }
  • Implementing unsafe traits:
    unsafe trait Foo {
        // methods go here
    }
    
    unsafe impl Foo for i32 {
        // method implementations go here
    }
    
    fn main() {}
  • 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:
    pub trait Iterator {
      type Item;
    
      fn next(&mut self) -> Option<Self::Item>;
    }
  • Default Generic Type Parameters and Operator Overloading:
    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 }
        );
    }
  • ⇒ The source Add trait is defined as (using a default generic value here):
    trait Add<Rhs=Self> {
      type Output;
    
      fn add(self, rhs: Rhs) -> Self::Output;
    }
  • Or we can specify the Rhs class:
    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))
      }
    }
  • Fully Qualified Syntax for Disambiguation: Calling Methods with the Same Name:
    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*");
      }
    }
  • 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:
    fn main() {
      let person = Human;
      Pilot::fly(&person);
      Wizard::fly(&person);
      person.fly();
    }
  • We could also write Human::fly(&person)
  • Disambiguation for trait functions:
    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());
    }
  • 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:
    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));
      }
    }
  • Using the Newtype Pattern to Implement External Traits on External Types:
    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);
    }
  • ⇒ Could consider implement Deref on the Wrapper type above to access all functions from Vec<T> directly.
  • Creating Type Synonyms with Type Aliases:
        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--
      }
  • Can also use generics:
    type Result<T> = std::result::Result<T, std::io::Error>;
  • The Never Type that Never Returns:
    fn bar() -> ! {
      // --snip--
    }
  • 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:
    fn generic<T: ?Sized>(t: &T) {
      // --snip--
    }
  • 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:
    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);
    }
  • Like above, this will also work:
        enum Status {
          Value(u32),
          Stop,
      }
    
      let list_of_statuses: Vec<Status> = (0u32..20).map(Status::Value).collect();
  • To be able to return a closure we have to use a Box:
    fn returns_closure() -> Box<dyn Fn(i32) -> i32> {
      Box::new(|x| x + 1)
    }
  • Example of how to build a declarative macro:
    #[macro_export]
    macro_rules! vec {
      ( $( $x:expr ),* ) => {
          {
              let mut temp_vec = Vec::new();
              $(
                  temp_vec.push($x);
              )*
              temp_vec
          }
      };
    }
  • When calling vec![1,2,3], the $x variable will collect the 1, 2 and 3 values, and this code will be generated:
    {
      let mut temp_vec = Vec::new();
      temp_vec.push(1);
      temp_vec.push(2);
      temp_vec.push(3);
      temp_vec
    }
  • 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:
    [lib]
    proc-macro = true
    
    [dependencies]
    syn = "1.0"
    quote = "1.0"
  • Implementation of procedural macro:
    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()
    }
  • note: can specify path to dependency in a cargo toml file with:
    hello_macro = { path = "../hello_macro" }
    hello_macro_derive = { path = "../hello_macro/hello_macro_derive" }
  • Code of the final project:
    // 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),
            }
        }
    }
    
  • public/books/rust_programming_language/intro.txt
  • Last modified: 2022/03/03 13:29
  • by 127.0.0.1