内容整理自: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
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
- absolutes
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
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
- not panic
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.