仅记录Rust特有的表达方式

基本的编程要素

数据类型

元祖类型

元祖有着固定的长度,一旦声明,元祖类型将不能改变size

fn main() {
	let tup: (i32, f64, u8) = (500, 6.4, 1); 
}

元祖元素的访问是通过.操作符号来进行的

fn main() {
    let x: (i32, f64, u8) = (500, 6.4, 1);

    let five_hundred = x.0;

    let six_point_four = x.1;

    let one = x.2;
}

空的元祖()有一个特殊的名称叫做unit,它类似于golang中的struct {}

数组类型

数组类型中的元素必须拥有同样的类型,并且在Rust中,数组类型是不可变长度的,并且数组的内存是在栈上申请的

数组的类型还可以表示为[data type; length]或者[initial value; length]

let a: [i32; 5] = [1, 2, 3, 4, 5];
let a = [3; 5]; // same as let a = [3, 3, 3, 3, 3]

函数

在Rust的函数定义中,必须给出传入参数的数据类型

Rust中,函数定义由一系列的statement构成,并且可选择的会包含一个expression

fn main() {
    let y = {
        let x = 3;
        x + 1
    };

    println!("The value of y is: {y}");
}

expression没有分号,如果你加上分号,那么它就会变成一个statement
Rust中,函数的返回值没有名字,但是必须要使用->指定返回值的类型,返回值和函数体花括号的最后一个expression是同义的,同时你也可以使用return在函数中间返回一个值,如果没有显示的返回一个值,例如最后是一个statement,则Rust默认返回一个()

基本控制流

If

if语句中的condition必须是一个bool类型的expression

三元表达式

fn main() {
    let condition = true;
    // else两边的值类型必须相同
    let number = if condition { 5 } else { 6 };

    println!("The value of number is: {number}");
}

循环

Rust使用loop语法创建一个死循环,同时loop语句可以使用continuebreak跳出,并且可以在break后面跟上loop语句的返回值

fn main() {
    let mut counter = 0;

    let result = loop {
        counter += 1;

        if counter == 10 {
            break counter * 2;
        }
    };

    println!("The result is {result}");
}

在有嵌套循环时,如果不想让break或者continue跳转到外一层的循环中,可以使用label,label必须以一个单引号开头命名

fn main() {
    let mut count = 0;
    'counting_up: loop {
        println!("count = {count}");
        let mut remaining = 10;

        loop {
            println!("remaining = {remaining}");
            if remaining == 9 {
                break;
            }
            if count == 2 {
                break 'counting_up;
            }
            remaining -= 1;
        }

        count += 1;
    }
    println!("End count = {count}");
}

Rust使用while语法创建一个条件循环

Rust使用for in语法遍历一个可遍历的对象

fn main() {
    let a = [10, 20, 30, 40, 50];

    for element in a {
        println!("the value is: {element}");
    }
}

同时for常常结合内置的Range语法使用,Range会产生一个范围内的所有整数

fn main() {
    for number in (1..4).rev() {
        println!("{number}!");
    }
    println!("LIFTOFF!!!");
}

所有权

  1. Read:变量可以被拷贝到另一个位置
  2. Write:数据可以被原地修改
  3. Own:数据可以被移动或者删除
    变量默认拥有RO权限,当被声明为mut之后,则拥有了W权限,编译器Borrow Checker的一个关键是引用可以使得变量暂时的失去某些权限,并且在引用的生命周期结束后重新获得这些权限
    image.png
    注意这里的num*num是拥有不同权限的,这是因为实际上权限是根据Path赋予的,Path包括以下几个方面:

可变引用提供对数据的唯一且非拥有的访问

image.png

所有权总结

fn main() {
  let s = String::from("Hello world");
  consume_a_string(s);
  println!("{s}"); // COMPILE ERROR: can't read `s` after moving it
}

fn consume_a_string(_s: String) {
  // om nom nom
}
fn main() {
  let mut s = String::from("Hello");
  let s_ref = &s;
  s.push_str(" world"); // COMPILE ERROR
  println!("{s_ref}");
}
fn main() {
  let mut s = String::from("Hello");
  let s_ref = &s;
  let s2 = *s_ref; // COMPILE ERROR
  println!("{s}");
}
fn main() {
  let mut s = String::from("Hello");
  let s_ref = &mut s;
  println!("{s}"); // COMPILE ERROR
  s_ref.push_str(" world");
}

切片类型

切片类型是一个非拥有类型的引用,切片属于一种富引用,即拥有元数据的指针

fn main() {
let s = String::from("hello world");

let hello: &str = &s[0..5];
let world: &str = &s[6..11];
let s2: &String = &s; 
}

同时支持省略区间表达,例如下面的写法是等价的:

let s = String::from("hello");

let len = s.len();

let slice = &s[0..len];
let slice = &s[..];

对String类型进行切片返回的是一个&str,同时Rust会隐式的转换&String和&str

fn main() {
    let my_string = String::from("hello world");

    // `first_word` works on slices of `String`s, whether partial or whole
    let word = first_word(&my_string[0..6]);
    let word = first_word(&my_string[..]);
    // `first_word` also works on references to `String`s, which are equivalent
    // to whole slices of `String`s
    let word = first_word(&my_string);

    let my_string_literal = "hello world";

    // `first_word` works on slices of string literals, whether partial or whole
    let word = first_word(&my_string_literal[0..6]);
    let word = first_word(&my_string_literal[..]);

    // Because string literals *are* string slices already,
    // this works too, without the slice syntax!
    let word = first_word(my_string_literal);
}

Struct

简写初始化

可以使用简写来初始化一个struct对象

struct User {
    active: bool,
    username: String,
    email: String,
    sign_in_count: u64,
}

// 因为build_user的参数和struct要求的一样,所以可以直接用email来替代email:email
fn build_user(email: String, username: String) -> User {
    User {
        active: true,
        username,
        email,
        sign_in_count: 1,
    }
}

省略初始化

可以使用省略语法来从利用一个对象中的成员初始化另一个对象,而只更新部分参数,注意这里的初始化使用的是=运算符,这里对于non-copyable对象是直接move,因此会移交所有权,因此在移交了non-copyable对象之后user1将不再可用,但若只利用copyable对象进行初始化则两者都将继续可用(例如只使用activesign_in_count进行初始化,而更新emailusername

struct User {
    active: bool,
    username: String,
    email: String,
    sign_in_count: u64,
}
fn main() {
   let user1 = User {
      email: String::from("[email protected]"),
      username: String::from("someusername123"),
      active: true,
      sign_in_count: 1,
   };
    // --snip--

    let user2 = User {
        email: String::from("[email protected]"),
		..user1 // 必须写在最后
    };
}

匿名struct

struct Color(i32, i32, i32);
struct Point(i32, i32, i32);

fn main() {
    let black = Color(0, 0, 0);
    let origin = Point(0, 0, 0);
}

Unit-Like Struct

tuple里面有unit类型(),struct也有类似的空结构体,称为Unit-Like Struct,当需要在某种类型上实现一个trait,但又没有要存储在类型本身中的任何数据时,类似单元的结构体可以很有用

struct AlwaysEqual;

fn main() {
    let subject = AlwaysEqual;
}

Struct的借用

struct的借用和tuple类似,也分为filed-levelstruct-level,例如如果借用了Point p中的某一个filed x,则pp.x都会失去某些权限,而p.y却不会

struct Point { x: i32, y: i32 }

fn print_point(p: &Point) {
    println!("{}, {}", p.x, p.y);
}

fn main() {
    let mut p = Point { x: 0, y: 0 };
    let x = &mut p.x;
	// COMPILE ERROR: can't READ p
    print_point(&p);
    *x += 1;
}
struct Point { x: i32, y: i32 }

fn print_point(y: &mut i32) {
    *y += 1;
}

fn main() {
    let mut p = Point { x: 0, y: 0 };
    let x = &mut p.x;
    // CHECK PASS: even we can write p.y!
    print_point(&mut p.y);
    *x += 1;
}

Struct 成员函数(method)

一个基础的成员函数实现是将函数搬到impl块中,并且第一个参数一定是self

#[derive(Debug)]
struct Rectangle {
    width: u32,
    height: u32,
}

impl Rectangle {
    fn area(&self) -> u32 {
        self.width * self.height
    }
	fn square(size: u32) -> Self {
		Self { 
			width: size,
			height: size,
		} 
	}
}

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

    println!(
        "The area of the rectangle is {} square pixels.",
        rect1.area()
    );
}

注意这里的&selfself: &Self的缩写,其意义与rectangle: &Rectangle一样,表达self为一个借用,且可以被声明为mutable的: area(&mut self),而通过直接使用 self 作为第一个参数来传递onwership的方式很少见; 这种技术通常用于当method将 self 转换成其他东西并且想阻止调用者在转换后使用原始实例时。

Rust允许struct中将成员变量和成员函数作相同命名,编译器可以通过是否使用括号来判断使用的是变量还是函数,但大多数情况下,与成员变量同名的函数被用作返回该成员变量。

Associated functions是指的在impl中定义的,但是没有使用self作为其第一个参数的函数,通常将函数的构造器作为Associated functions定义为new方法,但是new也不是规定的名字

impl Rectangle {
    fn square(size: u32) -> Self {
        Self {
            width: size,
            height: size,
        }
    }
}

调用Associated functions时,我们使用::语法,例如String::from()

Enum和模式匹配

定义enum可以给每个枚举量绑定一个类型,可以是简单类型,可以是struct甚至也可以是enum

fn main() {
enum IpAddr {
    V4(u8, u8, u8, u8),
    V6(String),
}

let home = IpAddr::V4(127, 0, 0, 1);

let loopback = IpAddr::V61");
}

并且可以给每个变量赋予有名的字段

enum Message {
    Quit,
    Move { x: i32, y: i32 },
    Write(String),
    ChangeColor(i32, i32, i32),
}

impl Message {
    fn call(&self) {
        // method body would be defined here
    }
}

let m = Message::Writefrom("hello");
m.call();
一个特殊的emun——option

null值的一个特点是当你试图去使用一个null值的时候,你会引发一个错误,null 试图表达的概念仍然是一个有用的概念:null 是当前无效或由于某种原因不存在的值。

Rust标准库定义了一个Option<T>的emun类型

enum Option<T> {
    None,
    Some(T),
}

并且你可以省去Option::而直接使用NoneSome

fn main() {
let some_number = Some(5);
let some_char = Some('e');

let absent_number: Option<i32> = None;
}

注意absent_number,编译器无法仅通过查看 None 值来推断相应 Some 变体将持有的类型。在这里,我们告诉 Rust,我们的意思是 absent_numberOption<i32> 类型。

为什么Option<T>有效的原因是TOption<T> 是两个不同的类型,他们之间不能够混淆使用,当我们面临一个Option<T>类型的变量的时候,我们要主动去思考(判断)它是否是一个有效值

模式匹配是以match开头的一个expression,匹配过程是进入第一个match的arm,执行其中的代码块并返回,模式匹配语句与代码块之间用=>隔开,如果没有{}形成的代码块,则将单个expression作为返回值

fn value_in_cents(coin: Coin) -> u8 {
    match coin {
        Coin::Penny => {
            println!("Lucky penny!");
            1
        }
        Coin::Nickel => 5,
        Coin::Dime => 10,
        Coin::Quarter => 25,
    }
}

模式匹配时可以进行变量绑定

#[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) => { // 这里state就绑定了UsState的值
            println!("State quarter from {:?}!", state);
            25
        }
    }
}

Option<T>做模式匹配是代码中非常常见的方式

    fn plus_one(x: Option<i32>) -> Option<i32> {
        match x {
            None => None,
            Some(i) => Some(i + 1),
        }
    }

    let five = Some(5);
    let six = plus_one(five);
    let none = plus_one(None);

模式匹配不仅可以用于emun,也可以用为常规值,但常规值通常具有无限的可能,这种情况下必须要注意的是,模式匹配必须是完备的(match表达式的arm囊括了所有可能的情况),即编译器不允许发生完全匹配不上的情况,且这个检查是在编译期执行的

  1. 作为最后一个分支,不提供匹配分支,而仅仅提供一个绑定的变量
  2. 作为最后一个分支,表示为默认分支,不需要使用绑定的变量,则使用_表达
    let dice_roll = 9;
    match dice_roll {
        3 => add_fancy_hat(),
        7 => remove_fancy_hat(),
        other => move_player(other),
    }

    fn add_fancy_hat() {}
    fn remove_fancy_hat() {}
    fn move_player(num_spaces: u8) {}

    let dice_roll = 9;
    match dice_roll {
        3 => add_fancy_hat(),
        7 => remove_fancy_hat(),
        _ => reroll(), // 返回()表示什么也不执行
    }

    fn add_fancy_hat() {}
    fn remove_fancy_hat() {}
    fn reroll() {}

对于上述第二种情况,还有一个更加方便的语法糖可以达到同样的效果,就是if letif let - else语句

    let config_max = Some(3u8);
    match config_max {
        Some(max) => println!("The maximum is configured to be {}", max),
        _ => (),
    }

等价于

    let config_max = Some(3u8);
    if let Some(max) = config_max {
        println!("The maximum is configured to be {}", max);
    }
    let mut count = 0;
    match coin {
        Coin::Quarter(state) => println!("State quarter from {:?}!", state),
        _ => count += 1,
    }

等价于

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

对于一个非copyable的变量的模式匹配,必须要注意其生命周期

fn main() {
let opt: Option<String> = 
    Somefrom("Hello world");

match opt {
    // _ became s
    Some(s) => println!("Some: {}", s), // opt moves to s
    None => println!("None!")
};

println!("{:?}", opt); // PANIC: opt lost read and own
}

这个时候可以使用&来匹配

fn main() {
let opt: Option<String> = 
    Somefrom("Hello world");

// opt became &opt
match &opt {
    Some(s) => println!("Some: {}", s),
    None => println!("None!")
};

println!("{:?}", opt);
}

packages、Crates And Modules

Crate是Rust编译器考虑的最小单位,通常一个.rs文件就是一个Crate,Crate可以是binary,也可以是library

Package是一个或者多个crates的集合,一个package包含一个Cargo.toml文件,用于描述如何编译代码,一个package可以包含任意数量的binary crates,但是最多只能有一个library crate,一个package可以将多个binary crate的文件放在src/bin中,每一个文件被视为一个单独的binary crate

Cargo约定src/main.rs是与package名称相同的binary cate的create root,src/lib.rs则是与package名称相同的library crate

下面是编译器构建一个项目的过程:

Path

Rust代码中形成的module tree支持像文件系统一样通过路径来访问定义的对象:

Re-exporting

当我们使用use关键字将一个名称引入作用域时,该名称在新的作用域中是私有的。为了使调用我们代码的代码能够像在其自身作用域中定义了该名称一样引用它,我们可以结合使用pubuse

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

在这个改变之前,外部代码必须通过使用路径 restaurant::front_of_house::hosting::add_to_waitlist() 来调用add_to_waitlist函数。现在,由于 pub use 从根模块re-exporting了 hosting 模块,外部代码可以使用路径 restaurant::hosting::add_to_waitlist() 来调用该函数。

容器

Vector

Rust内置了一个宏vec!来初始化vector

let v = vec![100, 32, 57];

String

String是实现在vec上的,并且是以utf-8编码的

拼接字符串可以使用以下的方式:

    let s1 = String::from("Hello, ");
    let s2 = String::from("world!");
    let s3 = s1 + &s2; // note s1 has been moved here and can no longer be used

+的函数定义是fn add(self, s: &str) -> String,注意第一个参数是非借用的,调用之后第一个字符串的生命周期结束,第二个操作单位是借用单位

对于更加复杂的拼接,可以使用和println类似的宏调用format!

    let s1 = String::from("tic");
    let s2 = String::from("tac");
    let s3 = String::from("toe");

    let s = format!("{s1}-{s2}-{s3}");

遍历字符串

for c in "Зд".chars() {
    println!("{c}");
}


for b in "Зд".bytes() {
    println!("{b}");
}

Hash Map

要在Rust中使用Hash Map,首先要使用use将其导入进来,因为它没有被默认引入

    use std::collections::HashMap;

    let mut scores = HashMap::new();

    scores.insertfrom("Blue"), 10;
    scores.insertfrom("Yellow"), 50;

insert的参数如果是可拷贝对象,则会将值复制一份,如果是所有权对象,则Hash Map会接管对象的所有权

使用Hash Map

    use std::collections::HashMap;

    let mut scores = HashMap::new();

    scores.insertfrom("Blue"), 10;
    scores.insertfrom("Yellow"), 50;

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

    let mut scores = HashMap::new();

    scores.insertfrom("Blue"), 10;
    scores.insertfrom("Yellow"), 50;

    for (key, value) in &scores {
        println!("{key}: {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);

错误处理

通过Result来处理可恢复(容忍)的错误

Result是一个枚举类型,T表示成功时需要返回的类型,E表示失败时需要返回的类型

enum Result<T, E> {
    Ok(T),
    Err(E),
}

use std::fs::File;

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

    let greeting_file = match greeting_file_result {
        Ok(file) => file,
        Err(error) => panic!("Problem opening the file: {:?}", error),
    };
}

Result及其变量是默认引入的,因此不需要加Result::

处理不同类型的错误

use std::fs::File;
use std::io::ErrorKind;

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

    let greeting_file = match greeting_file_result {
        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);
            }
        },
    };
}

Panic on Error的简写

use std::fs::File;

fn main() {
    let greeting_file = File::open("hello.txt").unwrap();
}
use std::fs::File;

fn main() {
    let greeting_file = File::open("hello.txt")
        .expect("hello.txt should be included in this project");
}

错误传递的简写

当一个函数的实现调用可能失败的函数时,不在函数本身内部处理错误,而是将错误返回给调用者,以便它可以决定如何处理。这被称为传播错误,并且将更多的控制权交给了调用代码,在那里可能有更多信息或逻辑来决定如何处理错误,而你在代码上下文中可用的信息则可能较少。

use std::fs::File;
use std::io::{self, Read};

fn read_username_from_file() -> Result<String, io::Error> {
    let username_file_result = File::open("hello.txt");

    let mut username_file = match username_file_result {
        Ok(file) => file,
        Err(e) => return Err(e),
    };

    let mut username = String::new();

    match username_file.read_to_string(&mut username) {
        Ok(_) => Ok(username),
        Err(e) => Err(e),
    }
}

错误传递在Rust中太常见了,以至于有一个简写?

use std::fs::File;
use std::io::{self, Read};

fn read_username_from_file() -> Result<String, io::Error> {
    let mut username_file = File::open("hello.txt")?;
    let mut username = String::new();
    username_file.read_to_string(&mut username)?;
    Ok(username)
}

?的实现与match基本一致,如果Result中是Ok,则把其中包含的值当作表达式的返回值,否则直接让整个函数返回错误

范型、Trait(接口)

Rust编译器要求事先声明泛型类型的预期功能,例如如果范型函数中使用了<,则一定需要将T的限制传递给范型函数,否则编译无法通过

fn largest<T>(list: &[T]) -> &T {
    let mut largest = &list[0];

    for item in list {
        if item > largest {
            largest = item;
        }
    }

    largest
}

fn main() {
    let number_list = vec![34, 50, 25, 100, 65];

    let result = largest(&number_list);
    println!("The largest number is {}", result);

    let char_list = vec!['y', 'm', 'a', 'q'];

    let result = largest(&char_list);
    println!("The largest char is {}", result);
}


$ cargo run
   Compiling chapter10 v0.1.0 (file:///projects/chapter10)
error[E0369]: binary operation `>` cannot be applied to type `&T`
 --> src/main.rs:5:17
  |
5 |         if item > largest {
  |            ---- ^ ------- &T
  |            |
  |            &T
  |
help: consider restricting type parameter `T`
  |
1 | fn largest<T: std::cmp::PartialOrd>(list: &[T]) -> &T {
  |             ++++++++++++++++++++++

For more information about this error, try `rustc --explain E0369`.
error: could not compile `chapter10` due to previous error

如果没有限制,泛型类型 T 就没有能力: 它不能被print、clone或mutate(尽管它可以被删除)。

struct 和 enum 的定义也可以采用范型

struct Point<T, U> {
    x: T,
    y: U,
}

impl<T> Point<T> {
    fn x(&self) -> &T {
        &self.x
    }
}

fn main() {
    let both_integer = Point { x: 5, y: 10 };
    let both_float = Point { x: 1.0, y: 4.0 };
    let integer_and_float = Point { x: 5, y: 4.0 };
	println!("p.x = {}", p.x());
}

enum Option<T> {
    Some(T),
    None,
}

enum Result<T, E> {
    Ok(T),
    Err(E),
}

在方法定义中使用范型

struct Point<T> {
    x: T,
    y: T,
}

impl<T> Point<T> {
    fn x(&self) -> &T {
        &self.x
    }
}

fn main() {
    let p = Point { x: 5, y: 10 };

    println!("p.x = {}", p.x());
}

你也可以单独为某个类型定义方法,这样该类型就具有此方法,而范型的其他类型则不具有此方法,并且,不能同时为某个类型和所有范型定义同名的方法

impl Point<f32> {
    fn distance_from_origin(&self) -> f32 {
        (self.x.powi(2) + self.y.powi(2)).sqrt()
    }
}

Trait

类似于其他编程语言中的接口

pub trait Summary { fn summarize(&self) -> String; }

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

如果pub trait Summary { fn summarize(&self) -> String; }中的summarize不是以分号结尾而是通过大括号实现,则将作为该Trait的默认实现,通过空的impl块可以使用默认实现impl Summary for NewsArticle {}

将Trait作为参数

pub fn notify(item: &impl Summary) {
    println!("Breaking news! {}", item.summarize());
}

任何实现了Summary的类型都可以作为该函数的参数,该语法还存在一个语法糖,称为Trait Bound

pub fn notify<T: Summary>(item: &T) {
    println!("Breaking news! {}", item.summarize());
}
// 该写法与上面的程序段等价

// 以下两个函数声明等价
pub fn notify(item1: &impl Summary, item2: &impl Summary) {

pub fn notify<T: Summary>(item1: &T, item2: &T) {

还可以同时声明多个Trait作为函数参数传递

// 以下两者等价
pub fn notify(item: &(impl Summary + Display)) {

pub fn notify<T: Summary + Display>(item: &T) {

为了避免函数声明繁琐,保证其可读性,引入了where关键字辅助声明函数

// 以下两者等价
fn some_function<T: Display + Clone, U: Clone + Debug>(t: &T, u: &U) -> i32 {

fn some_function<T, U>(t: &T, u: &U) -> i32
where
    T: Display + Clone,
    U: Clone + Debug,
{

返回Trait

可以将impl Trait作为返回值,但是不能够返回两种都实现了Trait的类型其中之一

fn returns_summarizable(switch: bool) -> impl Summary {
    if switch {
        NewsArticle {
            headline: String::from(
                "Penguins win the Stanley Cup Championship!",
            ),
            location: String::from("Pittsburgh, PA, USA"),
            author: String::from("Iceburgh"),
            content: String::from(
                "The Pittsburgh Penguins once again are the best \
                 hockey team in the NHL.",
            ),
        }
    } else {
        Tweet {
            username: String::from("horse_ebooks"),
            content: String::from(
                "of course, as you probably already know, people",
            ),
            reply: false,
            retweet: false,
        }
    }
}

// 编译无法通过

条件性的实现方法

可以条件性的为实现了某种Trait的类型实现另一种Trait

impl<T: Display> ToString for T {
    // --snip--
}

引用的Lifetime

Lifetime标注旨在告诉 Rust 多个引用的通用生命周期参数如何相互关联

采用以下的语法给变量标注lifetime

&i32        // a reference
&'a i32     // a reference with an explicit lifetime
&'a mut i32 // a mutable reference with an explicit lifetime

怎么使用Lifetime标注:

以下的代码无法通过编译:

fn longest(x: &str, y: &str) -> &str {
    if x.len() > y.len() {
        x
    } else {
        y
    }
}


$ cargo run
   Compiling chapter10 v0.1.0 (file:///projects/chapter10)
error[E0106]: missing lifetime specifier
 --> src/main.rs:9:33
  |
9 | fn longest(x: &str, y: &str) -> &str {
  |               ----     ----     ^ expected named lifetime parameter
  |
  = help: this function's return type contains a borrowed value, but the signature does not say whether it is borrowed from `x` or `y`
help: consider introducing a named lifetime parameter
  |
9 | fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
  |           ++++     ++          ++          ++

For more information about this error, try `rustc --explain E0106`.
error: could not compile `chapter10` due to previous error

而进行了Lifetime标注之后即可通过编译:

fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
    if x.len() > y.len() {
        x
    } else {
        y
    }
}

函数签名现在告诉 Rust,对于某个生命周期 'a,该函数采用两个参数,这两个参数都是字符串切片,其生命周期至少与生命周期 'a 一样长。函数签名还告诉 Rust,从函数返回的字符串切片的生命周期至少与生命周期 'a 一样长。实际上,这意味着longest函数返回的引用的生命周期与函数参数引用的值的较小生命周期相同。这些关系是我们希望 Rust 在分析这段代码时使用的。

当一个函数返回引用的时候,返回类型的生命周期参数需要与参数之一的生命周期参数匹配,因为如果不是这样的话,那么这个返回的引用一定指向函数体中新建的变量,那么这个引用在函数结束之后就一定会成为一个悬空引用(因为函数体中新建的变量一定会在函数结束的时候被free掉)

函数编程特性

闭包

Rust 的闭包是匿名函数,可以保存在变量中,也可以作为参数传递给其他函数。您可以在一个地方创建闭包,然后在其他地方调用闭包,以便在不同的上下文中对其进行评估。与函数不同,闭包可以从定义它们的作用域中捕获值。

#[derive(Debug, PartialEq, Copy, Clone)]
enum ShirtColor {
    Red,
    Blue,
}

struct Inventory {
    shirts: Vec<ShirtColor>,
}

impl Inventory {
    fn giveaway(&self, user_preference: Option<ShirtColor>) -> ShirtColor {
        user_preference.unwrap_or_else(|| self.most_stocked())
    }

    fn most_stocked(&self) -> ShirtColor {
        let mut num_red = 0;
        let mut num_blue = 0;

        for color in &self.shirts {
            match color {
                ShirtColor::Red => num_red += 1,
                ShirtColor::Blue => num_blue += 1,
            }
        }
        if num_red > num_blue {
            ShirtColor::Red
        } else {
            ShirtColor::Blue
        }
    }
}

fn main() {
    let store = Inventory {
        shirts: vec![ShirtColor::Blue, ShirtColor::Red, ShirtColor::Blue],
    };

    let user_pref1 = SomeRed;
    let giveaway1 = store.giveaway(user_pref1);
    println!(
        "The user with preference {:?} gets {:?}",
        user_pref1, giveaway1
    );

    let user_pref2 = None;
    let giveaway2 = store.giveaway(user_pref2);
    println!(
        "The user with preference {:?} gets {:?}",
        user_pref2, giveaway2
    );
}

unwarp_or_else接收一个没有参数的函数闭包,该闭包返回一个和unwarp_or_else调用者Option<T>T相同的类型,如果对一个None使用,则调用传入的函数闭包获取一个值,对一个Some(T)使用则包含的返回T类型的值

闭包通常在一个小的上下文环境中定义和使用,因此编译器可以像推断变量类型那样推断闭包的参数类型和返回值类型,当然也可以自行指定,以下比较了函数定义和闭包定义:

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  ;

需要注意的是,编译器对于闭包变量类型的推断是一次性的,这注定了闭包不能够像范型一样使用,能够接受多种类型的参数

闭包对于引用和所有权的作用

闭包会根据函数体决定要通过哪种形式捕获引用

fn main() {
    let list = vec![1, 2, 3];
    println!("Before defining closure: {:?}", list);

    let only_borrows = || println!("From closure: {:?}", list);

    println!("Before calling closure: {:?}", list);
    only_borrows();
    println!("After calling closure: {:?}", list);
}

闭包的函数体仅仅打印了list,所以其在定义的时候会申请一个其不可变的引用,而在使用完毕之后释放这个引用,由于不可变引用可以在同时申请多个,因此在定义之前,定义之后使用之前,使用之后都可以正常的申请和使用list的不可变引用

fn main() {
    let mut list = vec![1, 2, 3];
    println!("Before defining closure: {:?}", list);

    let mut borrows_mutably = || list.push(7);

    borrows_mutably();
    println!("After calling closure: {:?}", list);
}

这里闭包的定义必须申请一个可变引用,因此只有在使用完毕之后才能再申请一个引用,在定义和使用的这段时间内可以视为闭包持有一个list的可变引用

如果你想要强制闭包在使用环境中的值时获取所有权,即使闭包体并不严格需要所有权,你可以在参数列表之前使用move关键字。这通常在新建一个线程,需要将某些数据的所有权交给新线程的时候使用。

use std::thread;

fn main() {
    let list = vec![1, 2, 3];
    println!("Before defining closure: {:?}", list);

    thread::spawn(move || println!("From thread: {:?}", list))
        .join()
        .unwrap();
}

闭包对待捕获变量的方式决定了其实现的Trait

impl<T> Option<T> {
    pub fn unwrap_or_else<F>(self, f: F) -> T
    where
        F: FnOnce() -> T
    {
        match self {
            Some(x) => x,
            None => f(),
        }
    }
}

例如接收FnMutsort_by_key不能够接受FnOnce而顺利通过编译

#[derive(Debug)]
struct Rectangle {
    width: u32,
    height: u32,
}

fn main() {
    let mut list = [
        Rectangle { width: 10, height: 1 },
        Rectangle { width: 3, height: 5 },
        Rectangle { width: 7, height: 12 },
    ];

    let mut sort_operations = vec![];
    let value = String::from("by key called");

    list.sort_by_key(|r| {
        sort_operations.push(value);
        r.width
    });
    println!("{:#?}", list);
}

这里传递的闭包获取了value并将它的所有权转交给了sort_operations,这个闭包显然只能运行一次,因此其实现的是FnOnce,从而不能够作为sort_by_key的参数

fn can_we_return_ptr(x: String) -> &str {
    let y = String::from("we cant");
    &y
}