Rust学习笔记(3)

内容整理自:https://doc.rust-lang.org/book/title-page.html

Chapter 5: Using Structs to Structure Related Data

Define and Instantiating Structs

  • like tuples, can be different types
  • unlike tuples, you can name each data
  • more flexible than tuples
  • definition:
struct User {
    username: String,
    email: String,
    sign_in_count: u64,
    active: bool,
}

  • creating a instance
let user1 = User {
    email: String::from("[email protected]"),
    username: String::from("someusername123"),
    active: true,
    sign_in_count: 1,
};

// assign value to a field, entire instance must be mutable
user1.email = String::from("[email protected]");
  • use fn to return a new instance with Shorthand
fn build_user(email: String, username: String) -> User {
    User {
        email,
        username,
        active: true,
        sign_in_count: 1,
    }
}
  • Creating Instances From Other Instances With Struct Update Syntax
let user2 = User {
    email: String::from("[email protected]"),
    username: String::from("anotherusername567"),
    ..user1
};

The syntax .. specifies that the remaining fields not explicitly set should have the same value as the fields in the given instance.

  • Using Tuple Structs without Named Fields to Create Different Types
struct Color(i32, i32, i32);
struct Point(i32, i32, i32);

let black = Color(0, 0, 0);
let origin = Point(0, 0, 0);

Tuple structs are useful when you want to give the whole tuple a name and make the tuple be a different type from other tuples, and naming each field as in a regular struct would be verbose or redundant.

  • Unit-Like Structs Without Any Fields
    You can also define structs that don’t have any fields! These are called unit-like structs because they behave similarly to (), the unit type. Unit-like structs can be useful in situations in which you need to implement a trait on some type but don’t have any data that you want to store in the type itself.

Method Syntax

  • like fn
  • define in struct/enum/trait
  • first param is self
  • definition:
#[derive(Debug)]
struct Rectangle {
    width: u32,
    height: u32,
}

impl Rectangle {
    fn area(&self) -> u32 {
        self.width * self.height
    }
}

fn main() {
    let rect1 = Rectangle { width: 30, height: 50 };

    println!(
        "The area of the rectangle is {} square pixels.",
        rect1.area()
    );
}
  • Methods with More Parameters
fn main() {
    let rect1 = Rectangle { width: 30, height: 50 };
    let rect2 = Rectangle { width: 10, height: 40 };
    let rect3 = Rectangle { width: 60, height: 45 };

    println!("Can rect1 hold rect2? {}", rect1.can_hold(&rect2));
    println!("Can rect1 hold rect3? {}", rect1.can_hold(&rect3));
}

impl Rectangle {
    fn area(&self) -> u32 {
        self.width * self.height
    }

    fn can_hold(&self, other: &Rectangle) -> bool {
        self.width > other.width && self.height > other.height
    }
}
  • Associated Functions
impl Rectangle {
    fn square(size: u32) -> Rectangle {
        Rectangle { width: size, height: size }
    }
}

// call
let sq = Rectangle::square(3);

Chapter 6: Enums and Pattern Matching

Defining an Enum

enum IpAddrKind {
    V4,
    V6,
}
  • variant types
enum Message {
    Quit,
    Move { x: i32, y: i32 },
    Write(String),
    ChangeColor(i32, i32, i32),
}
  • define method
impl Message {
    fn call(&self) {
        // method body would be defined here
    }
}

let m = Message::Write(String::from("hello"));
m.call();
  • The Option Enum and Its Advantages Over Null Values
    • a value could be something or it could be nothing
    • Rust doesn’t have the null feature
    • the concept that null is trying to express is still a useful one: a null is a value that is currently invalid or absent for some reason.
    • Option
enum Option {
    Some(T),
    None,
}
* use Option
let some_number = Some(5);
let some_string = Some("a string");

let absent_number: Option = None;

Everywhere that a value has a type that isn’t an Option, you can safely assume that the value isn’t null

The match Control Flow Operator

enum Coin {
    Penny,
    Nickel,
    Dime,
    Quarter,
}

fn value_in_cents(coin: Coin) -> u8 {
    match coin {
        Coin::Penny => 1,
        Coin::Nickel => 5,
        Coin::Dime => 10,
        Coin::Quarter => 25,
    }
}
  • Patterns that Bind to Values
#[derive(Debug)] // so we can inspect the state in a minute
enum UsState {
    Alabama,
    Alaska,
    // --snip--
}

enum Coin {
    Penny,
    Nickel,
    Dime,
    Quarter(UsState),
}

fn value_in_cents(coin: Coin) -> u8 {
    match coin {
        Coin::Penny => 1,
        Coin::Nickel => 5,
        Coin::Dime => 10,
        Coin::Quarter(state) => {
            println!("State quarter from {:?}!", state);
            25
        },
    }
}
  • Matching with Option
fn plus_one(x: Option) -> Option {
    match x {
        None => None,
        Some(i) => Some(i + 1),
    }
}

let five = Some(5);
let six = plus_one(five);
let none = plus_one(None);
  • The _ Placeholder
let some_u8_value = 0u8;
match some_u8_value {
    1 => println!("one"),
    3 => println!("three"),
    5 => println!("five"),
    7 => println!("seven"),
    _ => (),
}

the _ will match all the possible cases that aren’t specified before it.

Concise Control Flow with if let

handle values that match one pattern while ignoring the rest

let mut count = 0;
if let Coin::Quarter(state) = coin {
    println!("State quarter from {:?}!", state);
} else {
    count += 1;
}

Chapter 7: Managing Growing Projects with Packages, Crates, and Modules

Packages and Crates

$ cargo new my-project
     Created binary (application) `my-project` package
$ ls my-project
Cargo.toml
src
$ ls my-project/src
main.rs
  • Cargo.thml give us a package
  • src/lib.rs is a library create, src/lib.rs is its crate root

Defining Modules to Control Scope and Privacy

cargo new --lib restaurant

// filenam: src/lib.rs
mod front_of_house {
    mod hosting {
        fn add_to_waitlist() {}

        fn seat_at_table() {}
    }

    mod serving {
        fn take_order() {}

        fn serve_order() {}

        fn take_payment() {}
    }
}
  • The module tree for the code:
crate
 └── front_of_house
     ├── hosting
     │   ├── add_to_waitlist
     │   └── seat_at_table
     └── serving
         ├── take_order
         ├── serve_order
         └── take_payment

Paths for Referring to an Item in the Module Tree

  • Path
    • absolutes
      • use crate
    • relative
      • self
      • super
      • identifier in current module
mod front_of_house {
    pub mod hosting {
        pub fn add_to_waitlist() {}
    }
}

pub fn eat_at_restaurant() {
    // Absolute path
    crate::front_of_house::hosting::add_to_waitlist();

    // Relative path
    front_of_house::hosting::add_to_waitlist();
}
  • all items in module are private by default, need to use pub keyword
  • Starting Relative Paths with super
fn serve_order() {}

mod back_of_house {
    fn fix_incorrect_order() {
        cook_order();
        super::serve_order();
    }

    fn cook_order() {}
}
  • struct with pub, need to specify which field to be pub
mod back_of_house {
    pub struct Breakfast {
        pub toast: String,
        seasonal_fruit: String,
    }

    impl Breakfast {
        pub fn summer(toast: &str) -> Breakfast {
            Breakfast {
                toast: String::from(toast),
                seasonal_fruit: String::from("peaches"),
            }
        }
    }
}

pub fn eat_at_restaurant() {
    // Order a breakfast in the summer with Rye toast
    let mut meal = back_of_house::Breakfast::summer("Rye");
    // Change our mind about what bread we'd like
    meal.toast = String::from("Wheat");
    println!("I'd like {} toast please", meal.toast);

    // The next line won't compile if we uncomment it; we're not allowed
    // to see or modify the seasonal fruit that comes with the meal
    // meal.seasonal_fruit = String::from("blueberries");
}
  • pub with enum, and the default for enum variants is to be public
mod back_of_house {
    pub enum Appetizer {
        Soup,
        Salad,
    }
}

pub fn eat_at_restaurant() {
    let order1 = back_of_house::Appetizer::Soup;
    let order2 = back_of_house::Appetizer::Salad;
}

Bringing Paths into Scope with the use Keyword

mod front_of_house {
    pub mod hosting {
        pub fn add_to_waitlist() {}
    }
}
// absolute path
use crate::front_of_house::hosting;
// relative path
// use self::front_of_house::hosting;

pub fn eat_at_restaurant() {
    hosting::add_to_waitlist();
    hosting::add_to_waitlist();
    hosting::add_to_waitlist();
}

use crate::front_of_house::hosting::add_to_waitlist;

pub fn eat_at_restaurant() {
    add_to_waitlist();
    add_to_waitlist();
    add_to_waitlist();
}
  • Providing New Names with the as Keyword
use std::fmt::Result;
use std::io::Result as IoResult;

fn function1() -> Result {
}

fn function2() -> IoResult<()> {
}
  • Re-exporting Names with pub use
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();
}
  • Using Nested Paths to Clean Up Large use Lists
use std::cmp::Ordering;
use std::io;
// same as above
use std::{cmp::Ordering, io};

// ------

use std::io;
use std::io::Write;
// same as above
use std::io::{self, Write};
  • The Glob Operator: bring all public items
use std::collections::*;

Separating Modules into Different Files

// Filename: src/lib.rs

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

// Filename: src/front_of_house.rs
pub mod hosting;

// Filename: src/front_of_house/hosting.rs
pub fn add_to_waitlist() {}

Chapter 8: Common Collections

A vector allows you to store a variable number of values next to each other.

Storing Lists of Values with Vectors

  • creating
let v: Vec = Vec::new();
let v = vec![1, 2, 3];
  • updating
let mut v = Vec::new();

v.push(5);
v.push(6);
v.push(7);
v.push(8);
  • Reading Elements of Vectors
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."),
}
  • Iterating over the Values in a Vector
let v = vec![100, 32, 57];
for i in &v {
    println!("{}", i);
}
  • mutable
let mut v = vec![100, 32, 57];
for i in &mut v {
    *i += 50;
}

Storing Keys with Associated Values in Hash Maps

  • Creating a New Hash Map
use std::collections::HashMap;

let mut scores = HashMap::new();

scores.insert(String::from("Blue"), 10);
scores.insert(String::from("Yellow"), 50);
  • hash maps are homogeneous: all of the keys must have the same type, and all of the values must have the same type.
  • Accessing Values in a Hash Map
use std::collections::HashMap;

let mut scores = HashMap::new();

scores.insert(String::from("Blue"), 10);
scores.insert(String::from("Yellow"), 50);

let team_name = String::from("Blue");
let score = scores.get(&team_name);
  • iterate
use std::collections::HashMap;

let mut scores = HashMap::new();

scores.insert(String::from("Blue"), 10);
scores.insert(String::from("Yellow"), 50);

for (key, value) in &scores {
    println!("{}: {}", key, value);
}
  • Updating a Hash Map
    • insert twice for a key will replace old value
    • Only Inserting a Value If the Key Has No Value
use std::collections::HashMap;

let mut scores = HashMap::new();
scores.insert(String::from("Blue"), 10);

scores.entry(String::from("Yellow")).or_insert(50);
scores.entry(String::from("Blue")).or_insert(50);

println!("{:?}", scores);   // {"Yellow": 50, "Blue": 10}
  • Updating a Value Based on the Old Value
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);  // {"world": 2, "hello": 1, "wonderful": 1}

or_insert method returns a mutable reference (&mut V) to the value for this key

Chapter 9: Error Handling

Recoverable Errors with panic!

use std::fs::File;

fn main() {
    let f = File::open("hello.txt");

    let f = match f {
        Ok(file) => file,
        Err(error) => {
            panic!("There was a problem opening the file: {:?}", error)
        },
    };
}

Matching on Different Errors

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!("Tried to create file but there was a problem: {:?}", e),
            },
            other_error => panic!("There was a problem opening the file: {:?}", other_error),
        },
    };
}

More clean code:

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!("Tried to create file but there was a problem: {:?}", error);
            })
        } else {
            panic!("There was a problem opening the file: {:?}", error);
        }
    });
}
  • Shortcuts for Panic on Error: unwrap and expect
    • If the Result value is the Ok variant, unwrap will return the value inside the Ok. If the Result is the Err variant, unwrap will call the panic! macro for us.
    • expect: customize panic! Error message
use std::fs::File;

fn main() {
    let f = File::open("hello.txt").expect("Failed to open hello.txt");
}
  • Propagating Errors
    • return the error to the calling code
use std::io;
use std::io::Read;
use std::fs::File;

fn read_username_from_file() -> Result {
    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),
    }
}
* A Shortcut for Propagating Errors: the ? Operator
use std::io;
use std::io::Read;
use std::fs::File;

fn read_username_from_file() -> Result {
    let mut f = File::open("hello.txt")?;
    let mut s = String::new();
    f.read_to_string(&mut s)?;
    Ok(s)
}

chaining method calls

use std::io;
use std::io::Read;
use std::fs::File;

fn read_username_from_file() -> Result {
    let mut s = String::new();

    File::open("hello.txt")?.read_to_string(&mut s)?;

    Ok(s)
}
  • The ? Operator Can Only Be Used in Functions That Return Result
use std::error::Error;
use std::fs::File;

fn main() -> Result<(), Box> {
    let f = File::open("hello.txt")?;

    Ok(())
}

Box mean “any kind of error.” Using ? in a main function with this return type is allowed.

To panic! or Not to panic!

  • Examples, Prototype Code, and Tests
    • the unwrap and expect methods are very handy when prototyping, before you’re ready to decide how to handle errors
  • Cases in Which You Have More Information Than the Compiler
    • can ensure by manually inspecting the code that you’ll never have an Err variant, it’s perfectly acceptable to call unwrap
use std::net::IpAddr;

let home: IpAddr = "127.0.0.1".parse().unwrap();
  • Guidelines for Error Handling

    • not panic
      • failure is expected, such as hit a rate limit
    • panic
      • operate invalid data
  • Creating Custom Types for Validation

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
        }
    }
      // getter
    pub fn value(&self) -> i32 {
        self.value
    }
}

Chapter 10: Generic Types, Traits, and Lifetimes

Validating References with Lifetimes

  • Lifetime Annotations in Function Signatures
    • When annotating lifetimes in functions, the annotations go in the function signature, not in the function body.
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
    if x.len() > y.len() {
        x
    } else {
        y
    }
}
* the lifetime of the reference returned by function is the same as the smaller of the lifetimes of the references passed in. 
  • Thinking in Terms of Lifetimes
    • When returning a reference from a function, the lifetime parameter for the return type needs to match the lifetime parameter for one of the parameters.
  • Lifetime Annotations in Struct Definitions
    • It’s possible for structs to hold references, but in that case we would need to add a lifetime annotation on every reference in the struct’s definition.
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 };
}
  • Lifetime Elision
    • 3 rules
  • The Static Lifetime
    • can live for the entire duration of the program
let s: &'static str = "I have a static lifetime.";
  • 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
    }
}

lifetimes are a type of generic, the declarations of the lifetime parameter 'a and the generic type parameter T go in the same list inside the angle brackets after the function name.

你可能感兴趣的:(Rust学习笔记(3))