跳转至

Rust- 基础

1 变量与常量

变量又分为可变变量和不可变变量;其中不可变变量和常量是不同的。

1.1 变量

变量以 let 修饰。 1. Rust 变量声明需要使用 let 关键字修饰 2. 变量的类型可以指定,也可以不指定,类型在变量名之后,如: let x: i32 = 2; 或者 let x = 2; 3. Rust 中变量 默认是不可变的,如果需要可变,需要使用 mut 关键字修饰,如:let mut x = 5; 4. 不可变变量只能有一次赋值,声明和赋值可以分开,如:let a; a=(1,2); 5. 变量赋值时,可以指定类型,比如指定整形是 i32 还是 u64,如 let x = 2i32 6. 变量赋值时,不指定类型,默认整形是 i32,浮点型是 f64 7. Rust 中未使用的变量编译时会有警告,如果需要取消警告,可以将变量名以 _ 开头,如 let _x = 2;, 获取使用属性修饰,如 #![allow(dead_code)]

1.2 常量

常量以 const 修饰。

#[allow(unused)]
#[derive(Debug)]
struct Point {
    x: i32,
    y: i32,
}

fn main() {
    const CENTER_POINTS: Point = Point { x: 0, y: 0 };
    println!("{:#?}", CENTER_POINTS);
}

1.3 变量遮蔽 (shadowing)

Rust 允许声明相同的变量名,在后面声明的变量会遮蔽掉前面声明的,如下所示:

#[allow(unused)]
fn main() {
    let x = 5;
    // 在main函数的作用域内对之前的x进行遮蔽
    let x = x + 1;

    {
        // 在当前的花括号作用域内,对之前的x进行遮蔽
        let x = x * 2;
        println!("The value of x in the inner scope is: {}", x);
    }

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

// 输出结果:
The value of x in the inner scope is: 12
The value of x is: 6

记住,当是可变变量时

2 类型

2.1 基本类型

  • unit 类型:(),这个就相对于什么数据都没有的类型。函数没有返回值时,其实就是返回了 () 单元类型。
  • char 类型:char
  • 字符串类型:&str字符串字面值是不可变的,因为被硬编码到程序代码中
  • bool 类型:bool
  • 浮点型:f32、f64
  • 整形:所有数字字面量允许使用 _ 作为可视分隔符,比如:1_234.0E+18f6410_000
长度 有符号类型 无符号类型
8 位 i8 u8
16 位 i16 u16
32 位 i32 u32
64 位 i64 u64
128 位 i128 u128
视架构而定 isize usize
  • 示例
fn main() {
    println!("hello world");
    let c: char = 'h';
    let b: bool = true;
    let s: &str = "hello";
    let str: String = String::from("hello");//动态字符串类型String
    println!("c={} b={} s={} str={}", c, b, s, str);
    let i: i32 = 2;
    let f: f64 = 2.0;
    println!("i={} f={}", i, f);
}

2.2 字符串(切片)

rust 中字符串有 2 种,一种是字面量字符串,类型是 &str,另一种是 String 类型。 - 示例

#![allow(unused)]
fn main() {
let s: &str = "Hello, world!";
}

2.2.1 String

String 类型的底层是 [u8] 数组

2.3 数组

title: rust数组与C++的不同?
1. rust数组定义语法如:`let a [i32:5] = [1, 2, 3, 4, 5];`,是使用`[]`来赋值,其中`[i32:5]`是`[类型:大小]`可以省略,而C++用`{}`。
2. rust数组不需要在数组名后加`[]`,如C++中是`char a[5];`
3. 数组访问2者是相同的,使用下标访问。
4. rust中函数或方法*参数一般不传数组*(因为不同大小的数组表示的是不同类型),而是传数组切片`&[T]`,而c++数组也相当于一个指针。

数组特点: - 存储在栈上 - 长度固定 - 元素必须有相同的类型 - 依次线性排列 语法示例:

fn main() {
    let a1: [i32;5] = [1, 2, 3, 4, 5];
    let a2 = [1, 2, 3, 4, 5];
    // 3重复5次
    let a3 = [3; 5];
    let elm = a1[0];
    println!("{:?}",a1);
    println!("{:?}",a2);
    println!("{:?}",a3);
    println!("{}",elm);
}
  • 可能输出
[1, 2, 3, 4, 5]  
[1, 2, 3, 4, 5]  
[3, 3, 3, 3, 3]  
1

2.4 动态数组 Vec

2.5 切片

rust中说的切片其实是**切片引用**,是对*数组的引用*或者*切片的引用*,签名是`&[Type]`,而数组签名是`[Type; Length]`。
字面量字符串其实是一种切片类型,叫做*字符串切片*,签名是`&str`,请注意字符串切片与其他类型切片的不同。

切片(Slice)跟数组相似,但是 切片的长度无法在编译期得知,因此你无法直接使用切片类型;因此我们使用 切片引用来代替切片,实际上 切片也是指切片引用。 - 语法:切片使用序列(Range)语法来确定切片的范围 - let s = &val[start..end] - let s = &val[start..=end] - 示例

#![allow(unused)]
fn main() {
    let s = String::from("hello");
    let len = s.len();
    // 字符串是切片,类型是&str
    let slice1 : &str = &s[0..len];
    // 对切片再次切片 
    let slice2 = &slice1[..2];
    println!("{:?}",s);
    println!("{:?}",slice1);
    println!("{:?}",slice2);
    let arr = [1, 2, 3];
    // i32切片
  let s1: &[i32] = &arr[0..=2];
  println!("{:?}",s1);
}
  • 可能的输出
"hello"  
"hello"  
"he"  
[1, 2, 3]

2.6 元组

rust 中元组的签名是 (Type1,Type2,...,Typen) 元组中每一个元素可以是不同类型。

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

可以通过索引访问元组中的数据:

fn main() {
    let tp:(&str, i32, &str) = ("hello",1,"world");
    println!("{:?}",tp);
    println!("0:{},1:{},2:{}",tp.0,tp.1,tp.2);
}

// 输出结果
("hello", 1, "world")
0:hello,1:1,2:world

或者解构后使用:

#[allow(unused)]
fn main() {
    let tp: (&str, i32, &str) = ("hello", 1, "world");
    let (x, y, z) = tp;
    println!("{:?}", tp);
    println!("0:{},1:{},2:{}", x, y, z);
}

// 输出结果
("hello", 1, "world")
0:hello,1:1,2:world

2.7 结构体

1. rust 中结构体和 C 结构体有点类似,有成员变量,不存在C++中的构造、析构函数。
2. rust 结构体也可以有方法(使用`impl`关键字声明),但是和C++类不同的是,rust*方法和数据是分离*的,不是在同一处定义。
3. 和C++一样,允许方法和字段(成员变量)同名。

rust 中结构体特点: 1. 和 C 一样使用 struct 关键字标识 2. 可以有多个成员变量

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

rust 结构体方法与其它语言的对比:

2.7.1 创建结构体

1. rust中初始化和C++一样,都可以使用花括号`{}`初始化
2. C++20开始支持指定初始化,但需要按成员变量声明顺序进行,而rust中不需要。
  1. 初始化实例时,每个字段 都需要进行初始化
  2. 初始化时的字段顺序 不需要 和结构体定义时的顺序一致
let user1 = User {
    email: String::from("someone@example.com"),
    username: String::from("someusername123"),
    active: true,
    sign_in_count: 1,
};

2.7.2 使用结构体

rust和C++中一致,使用`.`来访问成员变量
#![allow(unused)]
fn main() {
    // 默认变量是不可变的,因此想要修改变量,必须声明mut
    let mut user1 = User {
        email: String::from("someone@example.com"),
        username: String::from("someusername123"),
        active: true,
        sign_in_count: 1,
    };
    user1.email = String::from("anotheremail@example.com");
}

2.7.3 更新结构体

结构体变量赋值,是**所有权的转让**,因此除了基本类型外的其他类型,在赋值结束后是不能再次使用的。但没有被**转移的字段依然可以使用**,比如下面示例中`user2.email`可以使用,但`user2.username`不能使用。

rust 中有需要很方便的设计来更新结构体成员变量的值。 当从一个结构体给另一个结构体赋值时,可以使用 ..变量名 的方式,但必须在 结构体的尾部 使用

fn main() {
    let user1 = User {
        email: String::from("someone@example.com"),
        username: String::from("someusername123"),
        active: true,
        sign_in_count: 1,
    };
    let user2 = User {
        active: user1.active,
        username: user1.username,
        email: String::from("another@example.com"),
        sign_in_count: user1.sign_in_count,
    };
    let user3 = User{
        email: String::from("hello@example.com"),
        ..user2  //必须在结构体尾部
    };
    println!("{:?}",user3);
}

2.7.4 元组结构体 (Tuple Struct)

结构体必须要有名称,但是结构体的字段可以没有名称,这种 结构体长得很像元组,因此被称为元组结构体,例如:

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

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

元组结构体在你希望有一个整体名称,但是又不关心里面字段的名称时将非常有用。例如上面的 Point 元组结构体,众所周知 3D 点是 (x, y, z) 形式的坐标点,因此我们无需再为内部的字段逐一命名为:x, y, z

2.7.5 单元结构体 (Unit-like Struct)

单元结构体单元类型 很像,没有任何字段和属性,如果你定义一个类型,但是不关心该类型的内容, 只关心它的行为时,就可以使用 单元结构体

struct AlwaysEqual;
let subject = AlwaysEqual;

// 我们不关心 AlwaysEqual 的字段数据,只关心它的行为,因此将它声明为单元结构体,然后再为它实现某个特征
impl SomeTrait for AlwaysEqual {

}

2.8 枚举

rust中枚举很强大,不仅结合了C++中的限域枚举`enum class`风格,还结合了结构体风格,**每一个枚举值都可以有不同的类型**,甚至可以**存储值**。

2.8.1 无类型的枚举值

#[derive(Debug)]
enum PokerSuit {
  Clubs,
  Spades,
  Diamonds,
  Hearts,
}
fn main() {
    let heart = PokerSuit::Hearts;
    let diamond = PokerSuit::Diamonds;
    println!("{:?}", heart);
    println!("{:?}", diamond);
}
  • 可能的输出
Hearts  
Diamonds

2.8.2 有类型的枚举值

枚举值的类型可以是任意类型,指定类型时的方式却并不一样,如:
- 元组:`(T1,T2,..,Tn)`
- 匿名结构体:{v1:t1,..,vn:tn}
- 其它类型:`(T)`
  • 示例
enum Message {
    Quit,
    Move { x: i32, y: i32 },
    Write(String),
    ChangeColor(i32, i32, i32),
}

fn main() {
    let m1 = Message::Quit;
    let m2 = Message::Move{x:1,y:1};
    let m3 = Message::ChangeColor(255,255,0);
}

该枚举类型代表一条消息,它包含四个不同的成员: - Quit 没有任何关联数据 - Move 包含一个匿名结构体 - Write 包含一个 String 字符串 - ChangeColor 包含三个 i32

其它示例:

#![allow(unused)]
#[derive(Debug)]
enum PokerCard {
    Clubs(u8),
    Spades(u8),
    Diamonds(char),
    Hearts(char),
}

fn main() {
   let c1 = PokerCard::Spades(5);
   let c2 = PokerCard::Diamonds('A');

   println!("{:?}",c1);
   println!("{:?}",c2);
}
  • 可能的输出
Spades(5)  
Diamonds('A')

2.9 函数指针

Rust 种使用 fn 关键字来声明函数指针。 1. 声明函数: 使用 fn 关键字可以声明一个函数。例如:

fn add(x: i32, y: i32) -> i32 { x + y }
  1. 声明函数指针类型: fn 关键字还可用于声明函数指针类型。
fn add_one(x: usize) -> usize {
    x + 1
}

let ptr: fn(usize) -> usize = add_one;
assert_eq!(ptr(5), 6);

let clos: fn(usize) -> usize = |x| x + 5;
assert_eq!(clos(5), 10);
  1. 当然也可以使用 type 关键字为函数指针声明别名,这样更方便使用:
type MyFn = fn(i32, i32) -> i32;
let ptr1: MyFn = |x, y| x + y;
let ptr2: MyFn = |x, y| x - y;
let res1 = ptr1(5, 2);
let res2 = ptr2(5, 2);

这里 MyFn 是一个函数指针类型,表示接受两个 i32 参数并返回一个 i32 值的函数。 4. 使用函数指针: 函数指针可以用于传递函数地址,或者作为某些数据结构的字段。例如:

fn apply_function(f: fn(i32, i32) -> i32, x: i32, y: i32) -> i32 {
    f(x, y)
}

let result = apply_function(add, 3, 4);

这里 apply_function 函数接受一个函数指针 f,以及两个参数 xy,然后调用函数指针来执行相应的函数。 5. 使用闭包和 fn 混合: fn 关键字和闭包可以混合使用。函数指针和闭包都可以作为函数参数或返回值。例如:

fn operate(op: fn(i32, i32) -> i32, x: i32, y: i32) -> i32 {
    op(x, y)
}

let add_result = operate(add, 3, 4);
let multiply_result = operate(|a, b| a * b, 3, 4);

这里 operate 函数接受一个函数指针或闭包,并调用它来执行相应的操作。

2.9.1 安全性

除了根据其签名而有所不同外,函数指针还具有两种形式:安全和不安全。 普通 fn() 函数指针只能指向安全函数,而 unsafe fn() 函数指针可以指向安全或不安全函数。

fn add_one(x: usize) -> usize {
    x + 1
}

unsafe fn add_one_unsafely(x: usize) -> usize {
    x + 1
}

let safe_ptr: fn(usize) -> usize = add_one;

// ERROR: 不匹配的类型:预期正常 fn,发现不安全 fn let bad_ptr: fn(usize) -> usize=add_one_unsafely;

let unsafe_ptr: unsafe fn(usize) -> usize = add_one_unsafely;
let really_safe_ptr: unsafe fn(usize) -> usize = add_one;

3 解构

**解构也是通过模式匹配规则**来执行的。解构支持*结构体、枚举、元组、数组和引用*。
struct Struct {
    e: i32
}

fn main() {
    let (a, b, c, d, e);
    (a, b) = (1, 2);
    // _ 代表匹配一个值,但是我们不关心具体的值是什么,因此没有使用一个变量名而是使用了 _
    [c, .., d, _] = [1, 2, 3, 4, 5];
    Struct { e, .. } = Struct { e: 5 };
    assert_eq!([1, 2, 1, 4, 5], [a, b, c, d, e]);
}

4 impl 方法


1. rust中方法对应C++中的成员函数,也是和一个对象关联的。
2. rust中方法是通过关键字`impl`来定义的。
3. Rust 允许我们为一个结构体定义多个 `impl` 块
4. 方法中的`self`相当于C++中的`this`,表示当前对象。
  • 示例
#![allow(unused)]
fn main() {
struct Circle {
    x: f64,
    y: f64,
    radius: f64,
}

impl Circle {
    // new是Circle的关联函数,因为它的第一个参数不是self,且new并不是关键字
    // 这种方法往往用于初始化当前结构体的实例
    fn new(x: f64, y: f64, radius: f64) -> Circle {
        Circle {
            x: x,
            y: y,
            radius: radius,
        }
    }

    // Circle的方法,&self表示借用当前的Circle结构体
    fn area(&self) -> f64 {
        std::f64::consts::PI * (self.radius * self.radius)
    }
}
}

4.1 self

`&self` 其实是 `self: &Self` 的简写(注意大小写)

self 关键字有 3 种形式: 1. self :表示 所有权转移 到该方法中(这种形式用的较少,使用后导致调用方不再拥有所有权了,对象将释放) 2. &self :表示该方法对此 对象的不可变引用 3. &mut self :表示该方法对此 对象的可变引用

4.2 方法的调用

rust 种方法通过 对象.方法 的形式来使用,这种形式和 C++ 中相同;而 C++ 中还有指针通过 指针->方法 或者 (*指针).方法 的方式;rust 简化了这种场景,不管方法是 self&self&mut self 都只需要 对象.方法 形式访问。 - 示例

#![allow(unused)]
fn main() {
#[derive(Debug,Copy,Clone)]
struct Point {
    x: f64,
    y: f64,
}

impl Point {
   fn distance(&self, other: &Point) -> f64 {
       let x_squared = f64::powi(other.x - self.x, 2);
       let y_squared = f64::powi(other.y - self.y, 2);

       f64::sqrt(x_squared + y_squared)
   }
}
let p1 = Point { x: 0.0, y: 0.0 };
let p2 = Point { x: 5.0, y: 6.5 };
// 以下2种调用时等价的
p1.distance(&p2);
(&p1).distance(&p2);
}

4.3 关联函数

rust中结构体关联函数和C++中static类成员函数相同,访问方式也时`结构体::方法`的形式。

这种定义在 impl 中且没有 self 的函数被称之为 关联函数: 因为它没有 self,不能用 f.read() 的形式调用,因此它是一个函数而不是方法,它又在 impl 中,与结构体紧密关联,因此称为关联函数。

在之前的代码中,我们已经多次使用过关联函数,例如 String::from,用于创建一个动态字符串。

impl Rectangle {
    fn new(w: u32, h: u32) -> Rectangle {
        Rectangle { width: w, height: h }
    }
}

Rust 中有一个约定俗成的规则,使用 new 来作为构造器的名称,出于设计上的考虑,Rust 特地没有用 new 作为关键字

因为是函数,所以不能用 . 的方式来调用,我们需要用 :: 来调用,例如 let sq = Rectangle::new(3, 3);。这个方法位于结构体的命名空间中::: 语法用于关联函数和模块创建的命名空间。

4.4 为枚举实现方法

枚举类型之所以强大,不仅仅在于它好用、可以 同一化类型,还在于,我们可以像结构体一样,为枚举实现方法:

#![allow(unused)]
#[derive(Debug)]
enum Message {
    Quit,
    Move { x: i32, y: i32 },
    Write(String),
    ChangeColor(i32, i32, i32),
}

impl Message {
    fn call(&self) {
        // 在这里定义方法体
        println!("{:?}:",self);
        if let Message::Write(str) = self {
            println!("str={}", str.to_string())
        }
    }
}

fn main() {
    let m = Message::Write(String::from("hello"));
    m.call();
    let m = Message::Move{x:2,y:3};
    m.call();
}

// 输出结果:
Write("hello"):
str=hello
Move { x: 2, y: 3 }:

5 函数

rust中函数指的是**不和结构体关联**的函数。对应的是C++中非类成员函数。
rust中可以省略return关键字,默认将最后一个表达式的值返回。
  • 函数结构:fn func_name(param: type,...)->return_type{statment}
    • 无返回值时,->return_type 可以不写,或者是 ->()
    • 和 C++ 一样,使用 return 关键字返回,也可以不使用。
    • 和 C++ 一样,参数必须指定类型
    • 参数类型和变量声明一样,也是放在名称后面
    • rust 中函数可以放在任意位置,而 C++ 中要求使用的函数需要在当前位置之前出现,或者有函数声明。
  • 示例
fn sum(a: i64, b: i64) -> i64 {
    return a + b;
}
fn main() {
    println!("sum={}", sum(1, 2));
}

5.1 发散函数

发散函数是 永远不会返回 的函数,这个和没有返回值是不一样的。使用 ! 标记返回类型 - 示例

fn foo() -> ! {
    panic!("This call never returns.");
}

6 闭包 closure

rust中的闭包相当于C++中的Lambda表达式。

闭包是 一种匿名函数,它可以赋值给变量也可以作为参数传递给其它函数,不同于函数的是,它允许捕获调用者作用域中的值

fn main() {
   let x = 1;
   let sum = |y| x + y;

    assert_eq!(3, sum(2));
}

上面示例中闭包捕获了变量 x。 如果尝试调用闭包两次,第一次使用 String 类型作为参数而第二次使用 u32,则会得到一个错误:

let example_closure = |x| x;

let s = example_closure(String::from("hello"));
let n = example_closure(5);

编译报错内容:

error[E0308]: mismatched types
 --> src/main.rs
  |
  | let n = example_closure(5);
  |                         ^ expected struct `std::string::String`, found
  integer
  |
  = note: expected type `std::string::String`
             found type `{integer}`

第一次使用 String 值调用 example_closure 时,编译器推断 x 和此闭包返回值的类型为 String。接着这些类型被锁定进闭包 example_closure 中,如果尝试对同一闭包使用不同类型则会得到类型错误。

6.1 语法

这些都是有效的闭包定义,并在调用时产生相同的行为。参数和返回值类型标注是可选的,闭包定义会为每个参数和返回值推断一个具体类型。

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 去承接,因此尾部不需要分号。
  • 第二行展示了一个完整标注的闭包定义。
  • 第三行闭包定义中省略了类型标注。
  • 第四行去掉了可选的大括号,因为闭包体只有一行。

6.2 move 闭包

move 关键字能将闭包内使用的外部变量所有权转移到闭包内,这样,在闭包定义之后就不能使用这个变量了。

#![allow(unused)]
use std::ops::DerefMut;

fn main() {
    let x: Vec<i32> = vec![1, 2, 3 ,4];

    let equal_to_x = move|z: &mut Vec<i32>| {z.push(4); z.deref_mut() == x};

    //println!("can't use x here: {:?}", x);

    let mut y: Vec<i32> = vec![1, 2, 3];

    assert!(equal_to_x(&mut y));
}

7 生命周期

7.1 所有权 ownership

Rust**通过所有权来管理内存**。和 C++ 中 **手动管理内存的分配和释放** 不同。

7.1.1 所有权原则

  1. Rust 中每一个值都被一个变量所拥有,该变量被称为值的所有者
  2. 一个值同时只能被一个变量所拥有,或者说一个值只能拥有一个所有者
  3. 当所有者 (变量) 离开作用域范围时,这个值将被丢弃 (drop)

7.1.2 所有权转移

- 在进行赋值(let x = y)或通过值来传递函数参数(foo(x))的时候,资源的所有权(ownership)会发生转移。按照 Rust 的说法,这被称为资源的移动(move)。
- 注意:引用并不会发生所有权的转移。
  1. 基本类型间赋值,默认是通过 copy 来赋值的,不会有所有权的转移。因此赋值后依然可以访问。
  2. 其他类型的变量赋值(let a = b),默认是将所有权从 a 转移到 b,因此,赋值结束后,a 就不能再访问了。如果通过深拷贝的方式来赋值,也不会有所有权的转移。
  3. 通过值来传递函数参数(foo(x))(注意不是引用) 时, x 的所有权被转移到函数内部,在调用函数之后,x 将不能使用。
// 此函数取得堆分配的内存的所有权
fn destroy_box(c: Box<i32>) {
    println!("Destroying a box that contains {}", c);

    // `c` 被销毁且内存得到释放
}

fn main() {
    // 栈分配的整型
    let x = 5u32;

    // 将 `x` *复制*到 `y`——不存在资源移动
    let y = x;

    // x y 是基本类型,是copy,因此两个值各自都可以使用
    println!("x is {}, and y is {}", x, y);

    // `a` 是一个指向堆分配的整数的指针
    let a = Box::new(5i32);

    println!("a contains: {}", a);

    // *移动* `a` 到 `b`
    let b = a;
    // 把 `a` 的指针地址(而非数据)复制到 `b`。现在两者都指向
    // 同一个堆分配的数据,但是现在是 `b` 拥有它。

    // 报错!`a` 不能访问数据,因为它不再拥有那部分堆上的内存。
    //println!("a contains: {}", a);
    // 试一试 ^ 去掉此行注释

    // 此函数从 `b` 中取得堆分配的内存的所有权
    destroy_box(b);

    // 此时堆内存已经被释放,这个操作会导致解引用已释放的内存,而这是编译器禁止的。
    // 报错!和前面出错的原因一样。
    //println!("b contains: {}", b);
    // 试一试 ^ 去掉此行注释
}

7.2 引用

引用也被称为借用(borrow),机制可以让所有权转移实现的变量传递变得简洁。
**切片和字符串字面量或者字符串切片是一种引用类型。**

rust 引用类似于 C++ 中变量的指针和引用的结合体(虽然叫引用,但需要解引用,就形同 C++ 中解指针一样)。

编译器(通过借用检查)静态地保证了引用 总是 指向有效的对象。也就是说,当存在引用指向一个对象时,该对象不能被销毁

// 此函数取得一个 box 的所有权并销毁它
fn eat_box_i32(boxed_i32: Box<i32>) {
    println!("Destroying box that contains {}", boxed_i32);
}

// 此函数借用了一个 i32 类型
fn borrow_i32(borrowed_i32: &i32) {
    println!("This int is: {}", borrowed_i32);
}

fn main() {
    // 创建一个装箱的 i32 类型,以及一个存在栈中的 i32 类型。
    let boxed_i32 = Box::new(5_i32);
    let stacked_i32 = 6_i32;

    // 借用了 box 的内容,但没有取得所有权,所以 box 的内容之后可以再次借用。
    // 译注:请注意函数自身就是一个作用域,因此下面两个函数运行完成以后,
    // 在函数中临时创建的引用也就不复存在了。
    borrow_i32(&boxed_i32);
    borrow_i32(&stacked_i32);

    {
        // 取得一个对 box 中数据的引用
        let _ref_to_i32: &i32 = &boxed_i32;

        // 报错!
        // 当 `boxed_i32` 里面的值之后在作用域中被借用时,不能将其销毁。
        eat_box_i32(boxed_i32);
        // 改正 ^ 注释掉此行

        // 在 `_ref_to_i32` 里面的值被销毁后,尝试借用 `_ref_to_i32`
        //(译注:如果此处不借用,则在上一行的代码中,eat_box_i32(boxed_i32)可以将 `boxed_i32` 销毁。)
        borrow_i32(_ref_to_i32);
        // `_ref_to_i32` 离开作用域且不再被借用。
    }

    // `boxed_i32` 现在可以将所有权交给 `eat_i32` 并被销毁。
    //(译注:能够销毁是因为已经不存在对 `boxed_i32` 的引用)
    eat_box_i32(boxed_i32);
}

7.2.1 解引用

&变量 引用,用 *变量 解引用:

fn main() {
    let x: i32 = 5;
    let y: &i32 = &x;
    assert_eq!(5, x);
    assert_eq!(5, *y);
}

注意,在函数参数定义引用变量时,使用不需要解引用:

// 此函数借用了一个 i32 类型
fn borrow_i32(borrowed_i32: &i32) {
    println!("This int is: {}", borrowed_i32);
}

7.2.2 不可变引用

rust 默认的引用是不可变的,即你无法修改引用的数据,相当于 C++ 中 const int * pi=2;,不能修改 pi 的值。

7.2.3 可变引用

要想使用可变引用只需加上 mut 关键字即可。可变引用会借用引用的数据,当可变引用不再使用时,就归还了。 可变引用的限制如下: 1. 只能有一个可变引用 2. 可变引用不能和不可变引用同时存在。 - 示例

fn main() {
    let mut s = String::from("hello");
    change(&mut s);
}

fn change(some_string: &mut String) {
    some_string.push_str(", world");
}

修改可变引用的数据:

#![allow(unused)]
fn main() {
    #[derive(Debug)]
    struct Point {
        x: i32,
        y: i32,
    }
    let mut a = Point { x: 1, y: 2 };
    println!("{a:?}");
    // b可变引用a,这样才能通过b修改a中数据
    let b = &mut a;
    b.x = 3;
    println!("{a:?}");
    // 首先c是可变的,其次可变引用a,这样才能修改c
    let mut c = &mut a;
    *c = Point { x: 3, y: 5 };
    println!("{a:?}");
}

// 输出结果
Point { x: 1, y: 2 }
Point { x: 3, y: 2 }
Point { x: 3, y: 5 }

同一时间内只允许 一次 可变借用。仅当最后一次使用可变引用 之后,原始数据才可以再次借用。

struct Point { x: i32, y: i32, z: i32 }

fn main() {
    let mut point = Point { x: 0, y: 0, z: 0 };

    let borrowed_point = &point;
    let another_borrow = &point;

    // 数据可以通过引用或原始类型来访问
    println!("Point has coordinates: ({}, {}, {})",
                borrowed_point.x, another_borrow.y, point.z);

    // 报错!`point` 不能以可变方式借用,因为当前还有不可变借用。
    // let mutable_borrow = &mut point;
    // TODO ^ 试一试去掉此行注释

    // 被借用的值在这里被重新使用
    println!("Point has coordinates: ({}, {}, {})",
                borrowed_point.x, another_borrow.y, point.z);

    // 不可变的引用不再用于其余的代码,因此可以使用可变的引用重新借用。
    let mutable_borrow = &mut point;

    // 通过可变引用来修改数据
    mutable_borrow.x = 5;
    mutable_borrow.y = 2;
    mutable_borrow.z = 1;

    // 报错!不能再以不可变方式来借用 `point`,因为它当前已经被可变借用。
    // let y = &point.y;
    // TODO ^ 试一试去掉此行注释

    // 报错!无法打印,因为 `println!` 用到了一个不可变引用。
    // println!("Point Z coordinate is {}", point.z);
    // TODO ^ 试一试去掉此行注释

    // 正常运行!可变引用能够以不可变类型传入 `println!`
    println!("Point has coordinates: ({}, {}, {})",
                mutable_borrow.x, mutable_borrow.y, mutable_borrow.z);

    // 可变引用不再用于其余的代码,因此可以重新借用
    let new_borrowed_point = &point;
    println!("Point now has coordinates: ({}, {}, {})",
             new_borrowed_point.x, new_borrowed_point.y, new_borrowed_point.z);
}

7.2.4 悬垂引用 (Dangling References)

rust 悬垂引用和 C++ 悬垂指针一样,不能返回一个局部变量的引用。 - 示例

fn main() {
    let reference_to_nothing = dangle();
}
fn dangle() -> &String {
    let s = String::from("hello");
    &s //出了函数,s被释放,变悬垂引用
}

7.3 可变性

rust 中默认变量是不可变的,如 let a =1u32;,此处 a 是只读的,不能被修改。当使用 mut 修饰时,此时变量就是可变的,具有外部可见性。

7.3.1 外部可变性

mut 修饰的变量具有外部可见性。

7.3.2 内部可变性

内部可变性Interior mutability)是 Rust 中的一个设计模式,它允许你即使在有不可变引用时也可以改变数据,这通常是借用规则所不允许的。为了改变数据,该模式在数据结构中使用 unsafe 代码来模糊 Rust 通常的可变性和借用规则。简单来说,在不可变值内部改变值就是 内部可变性 模式。

可以使用 RefCell<T> 来声明内部可变性。

fn main() {
    use std::cell::RefCell;
    #[derive(Debug)]
    pub struct Foo {
        i: RefCell<u32>,
    }

    impl Foo {
        pub fn new() -> Self {
            Self { i: RefCell::new(0) }
        }
        pub fn set_1(&mut self, v: u32) {
            *self.i.borrow_mut() = v;
        }
        pub fn set_2(&self, v: u32) {
            *self.i.borrow_mut() = v;
        }
    }
    let mut f1 = Foo::new();
    // 只能用可变变量f1调用set_1,不可变变量f2调用会报错
    f1.set_1(1);
    let f2 = Foo::new();
    f2.set_2(2);
    println!("{f1:?}");
    println!("{f2:?}");
}

// 输出结果
Foo { i: RefCell { value: 2 } }
Foo { i: RefCell { value: 3 } }

7.4 生命周期

https://course.rs/basic/lifetime.html#%E7%BB%93%E6%9E%84%E4%BD%93%E4%B8%AD%E7%9A%84%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F

8 流程控制

`if、for、while、loop`与C++语法上最大区别是:条件表达式不需要`()`包裹。
Rust 也有`break、continue`关键字,功能和C++完全一致,但break可以**带一个返回值**,有些类似 `return`。

8.1 if

Rust if 和C++中很像,都有`if、else、else if`这3种判断条件。但有2点不同:
1. Rust条件表达式不需要用`()`包裹。
2. Rust if语句是表达式,可以返回结果,但各分支返回值必须相同;而C++中不能。
  • 示例
#![allow(unused)]
fn main() {
    let number = 6;

    let s = if number % 4 == 0 {
        "number is divisible by 4"
    } else if number % 3 == 0 {
        "number is divisible by 3"
    } else if number % 2 == 0 {
        "number is divisible by 2"
    } else {
        "number is not divisible by 4, 3, or 2"
    };
    println!("{}",s);
}
  • 可能的输出
number is divisible by 3

8.2 if let

有时会遇到只有一个模式的值需要被处理,其它值直接忽略的场景,如果用 match 来处理就要写成下面这样:

    let v = Some(3u8);
    match v {
        Some(3) => println!("three"),
        _ => (),
    }

使用 if let 如下:

#![allow(unused)]
fn main() {
    let v = Some(3u8);
    // if 也可以(注意后面的==号)
    if Some(3) == v {
    println!("three 1");
    }
    // if let 也可以,(注意后面的=号)
    if let Some(3) = v {
    println!("three 2");
    }
}

从上面代码能看出,有些场景下使用 if let 和 if 作用相同,但有些场景 (比如变量覆盖) 就不行,如下

fn main() {
   let age = Some(30);
   println!("在匹配前,age是{:?}",age);
   // 此代码不通过,需要使用`if let Some(age) = age`
   /*if Some(age) == age {
       println!("匹配出来的age是{}",age);
   }*/
   // if let 可以
   if let Some(age) = age {
       println!("匹配出来的age是{}",age);
   }
   println!("在匹配后,age是{:?}",age);
}
  • 可能的输出
在匹配前,age是Some(30)  
匹配出来的age是30  
在匹配后,age是Some(30)

8.3 for

Rust 中 for 语法有 3 种格式,如下: 1. 循环集合,只关注值,如 for 元素 in 集合 {//表达式或语句} 2. 循环集合,同时关注索引、值,如:for (index, value) in 集合 {//表达式或语句} 3. 循环集合,忽略任何值、索引,如:for _ in 集合 {//表达式或语句}

  • 示例
fn main() {
    for i in 1..=5 {
        println!("{}", i);
    }
}
  • 可能的输出
1  
2  
3  
4  
5

for 循环中也能使用可变 mut、引用 & 语法,总结如下:

使用方法 等价使用方式 所有权
for item in collection for item in IntoIterator::into_iter(collection) 转移所有权
for item in &collection for item in collection.iter() 不可变借用
for item in &mut collection for item in collection.iter_mut() 可变借用

8.4 while

Rust中while和C++中while一样。
  • 示例
fn main() {
    let mut n = 0;
    while n <= 5  {
        println!("{}!", n);
        n = n + 1;
    }
    println!("我出来了!");
}

8.5 while let

if let 相似 - 示例

#![allow(unused)]
fn main() {
// Vec是动态数组
let mut stack = Vec::new();

// 向数组尾部插入元素
stack.push(1);
stack.push(2);
stack.push(3);

// stack.pop从数组尾部弹出元素
while let Some(top) = stack.pop() {
    println!("{}", top);
}
}
  • 可能的输出
3  
2  
1

8.6 loop

loop 有点类似与 while 1,循环一直进行,除非循环体主动 breakreturnloop 还是一个表达式,因此可以返回一个值。 - 示例

fn main() {
    let mut counter = 0;
    let result = loop {
        counter += 1;
        if counter == 10 {
            break counter * 2;
        }
    };
    println!("The result is {}", result);
}
  • 可能的输出
The result is 20

8.7 break、continue

Rust中break、continue功能和C++只能一样,但break功能更强,**可以单独使用,也可以带一个返回值**,有些类似 `return`

9 模式匹配

9.1 可反驳的(refutable)和不可反驳的(irrefutable)

模式有两种形式:可反驳的(refutable)和不可反驳的(irrefutable)。 - 不可反驳的(irrefutable):能 匹配任何可能值 的模式是不可反驳的。如 let x = 5; 中的 x,因为 x 可以匹配任何值所以不可能匹配失败。 - 可反驳的(refutable):对于可能的值,存在匹配失败 的情况的模式是可反驳的。如,表达式 if let Some (x) = a_value 中的 Some (x),如果变量 a_value 中的值是 None 而不是 Some,那么与模式 Some (x) 就会匹配失败。

9.2 match

1. rust中的match功能上和C++中的`switch case`一样,但语法格式完全不一样。
2. rust中match是一个表达式,因此match分支需要返回一个值。

match 语法格式如下:

match target {
    模式1 => 表达式1,
    模式2 => {
        语句1;
        语句2;
        表达式2
    },
    _ => 表达式3
}

有以下几点值得注意: 1. match 的匹配必须要穷举出所有可能,因此这里用 _ 来代表未列出的所有可能性 2. match 的每一个分支都必须是一个表达式,且所有分支的表达式最终返回值的类型必须相同 3. X | Y,类似逻辑运算符 ,代表该分支可以匹配 X 也可以匹配 Y,只要满足一个即可。 4. 当 match 的是数值类型时还可以使用 1..=5 表示匹配数值 1~5。 - 示例:请注意 println! 宏展开后其实也是个表达式。

enum Direction {
    East,
    West,
    North,
    South,
}

fn main() {
    let dire = Direction::South;
    match dire {
        Direction::East => println!("East"),
        Direction::North | Direction::South => {
            println!("South or North");
        },
        _ => println!("West"),
    };
}

9.2.1 模式绑定

在匹配有类型的枚举时,可以从枚举值中取出值。 - 示例

enum Action {
    Say(String),
    MoveTo(i32, i32),
    ChangeColorRGB(u16, u16, u16),
}

fn main() {
    let actions = [
        Action::Say("Hello Rust".to_string()),
        Action::MoveTo(1,2),
        Action::ChangeColorRGB(255,255,0),
    ];
    for action in actions {
        match action {
            Action::Say(s) => {
                println!("{}", s);
            },
            Action::MoveTo(x, y) => {
                println!("point from (0, 0) move to ({}, {})", x, y);
            },
            Action::ChangeColorRGB(r, g, _) => {
                println!("change color into '(r:{}, g:{}, b:0)', 'b' has been ignored",
                    r, g,
                );
            }
        }
    }
}

9.2.2 匹配守卫match guard

在 match 分支模式之后的增加一个额外 if 条件,当所有条件满足时就匹配

#[allow(unused)]
fn main() {
    let x = 4;
    let y = false;

    match x {
        4 | 5 | 6 if y => println!("yes"),
        _ => println!("no"),
    }
}

9.2.3 通过序列 ..= 匹配值的范围

..= 语法允许你匹配一个闭区间序列内的值。

let x = 5;

match x {
    1..=5 => println!("one through five"),
    _ => println!("something else"),
}

如果 x 是 1、2、3、4 或 5,第一个分支就会匹配。这相比使用 | 运算符表达相同的意思更为方便;相比 1..=5,使用 | 则不得不指定 1 | 2 | 3 | 4 | 5 这五个值,而使用 ..= 指定序列就简短的多,比如希望匹配比如从 1 到 1000 的数字的时候!

序列只允许用于数字或字符类型,原因是:它们可以连续,同时编译器在编译期可以检查该序列是否为空,字符和数字值是 Rust 中仅有的可以用于判断是否为空的类型。

如下是一个使用字符类型序列的例子:

let x = 'c';

match x {
    'a'..='j' => println!("early ASCII letter"),
    'k'..='z' => println!("late ASCII letter"),
    _ => println!("something else"),
}

9.3 @绑定

@(读作 at)运算符允许为一个字段绑定另外一个变量。下面例子中,我们希望测试 Message::Hello 的 id 字段是否位于 3..=7 范围内,同时也希望能将其值绑定到 id_variable 变量中以便此分支中相关的代码可以使用它。我们可以将 id_variable 命名为 id,与字段同名,不过出于示例的目的这里选择了不同的名称。

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

上例会打印出 Found an id in range: 5。通过在 3..=7 之前指定 id_variable @,我们捕获了任何匹配此范围的值并同时将该值绑定到变量 id_variable 上。

第二个分支只在模式中指定了一个范围,id 字段的值可以是 10、11 或 12,不过这个模式的代码并不知情也不能使用 id 字段中的值,因为没有将 id 值保存进一个变量。

最后一个分支指定了一个没有范围的变量,此时确实拥有可以用于分支代码的变量 id,因为这里使用了结构体字段简写语法。不过此分支中没有像头两个分支那样对 id 字段的值进行测试:任何值都会匹配此分支。

当你既想要限定分支范围,又想要使用分支的变量时,就可以用 @ 来绑定到一个新的变量上,实现想要的功能。

9.3.1 @前绑定后解构 (Rust 1.56 新增)

使用 @ 还可以在绑定新变量的同时,对目标进行解构:

#[derive(Debug)]
struct Point {
    x: i32,
    y: i32,
}

fn main() {
    // 绑定新变量 `p`,同时对 `Point` 进行解构
    let p @ Point {x: px, y: py } = Point {x: 10, y: 23};
    println!("x: {}, y: {}", px, py);
    println!("{:?}", p);


    let point = Point {x: 10, y: 5};
    if let p @ Point {x: 10, y} = point {
        println!("x is 10 and y is {} in {:?}", y, p);
    } else {
        println!("x was not 10 :(");
    }
}

9.3.2 @新特性 (Rust 1.53 新增)

考虑下面一段代码:

fn main() {
    match 1 {
        num @ 1 | 2 => {
            println!("{}", num);
        }
        _ => {}
    }
}

编译不通过,是因为 num 没有绑定到所有的模式上,只绑定了模式 1,你可能会试图通过这个方式来解决:

num @ (1 | 2)

但是,如果你用的是 Rust 1.53 之前的版本,那这种写法会报错,因为编译器不支持。

10 格式化输出

10.1 格式化输出宏

https://course.rs/basic/formatted-output.html

`print!、println!、format!`其实是一个*表达式*。
  • print!: 将格式化文本输出到标准输出,不带换行符
  • println!: 同上,但是在行的末尾添加换行符
  • format!: 将格式化文本输出到 String 字符串 以上 3 个,又支持 3 种占位标志:
  • {}:适用于实现了 std::fmt::Display 特征的类型
  • {:?} :适用于实现了 std::fmt::Debug 特征的类型,在结构体前声明 #[derive(Debug)],就可以不自定义格式化器。
  • {:#?}:和 {:?} 类似,但输出格式不同
#![allow(unused)]
fn main() {
    #[derive(Debug)]
    struct Point {
        x: i32,
        y: i32,
    }
    let p = Point { x: 10, y: 11 };
    let px: i32 = p.x;
    println!("{:#?}", p);
    println!("{:?}", p);
    println!("{p:?}");
}

// 输出结果:
Point {
    x: 10,
    y: 11,
}
Point { x: 10, y: 11 }
Point { x: 10, y: 11 }

10.2 自定义输出格式

只要为类型实现 Display 特征,就能控制输出格式

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

// 输出结果
// w = [hello, world]

11 预导入包

https://www.rustwiki.org.cn/zh-CN/reference/names/preludes.html https://www.rustwiki.org.cn/zh-CN/std/prelude/index.html

12 增强功能

12.1 序列 (Range)

Range 用来生成连续的数值。 - 语法格式: - num_start..num_end :从 num_start 到 num_end-1 的连续数字,例如 1..5,生成从 1 到 4 的连续数字 - num_start..=num_end :从 num_start 到 num_end 的连续数字,1..=5,生成从 1 到 5 的连续数字 - 示例

fn fun1(){
    for i in 0..=5{
        println!("{}",i);
    }
}
fn main() {
    fun1();
}
  • 可能结果
0  
1  
2  
3  
4  
5

12.2 NaN

  • 功能:对于数学上未定义的结果,例如对负数取平方根 -42.1.sqrt() ,会产生一个特殊的结果:Rust 的浮点数类型使用 NaN (not a number) 来处理这些情况。
  • 判断数值是否合规:使用 is_nan() 函数
  • 示例
fn main() {
    let x = (-42.0_f32).sqrt();
    if x.is_nan() {
        println!("未定义的数学行为")
    }
}
  • 可能的输出
未定义的数学行为

12.3 通配符 _

在 rust 中 _ 符号是一个统配符,表示可以匹配任意内容,通常可以用在一下几处: 1. match 表达式结尾分支,当前面分支没有匹配时,_ 就会匹配任意内容。 2. for in 循环中,当我们不关心值时,用 _ 忽略值 3. _ 甚至可以作为变量名前缀,表示未使用的变量,编译不会警告。

  • 示例 1
for _ in 0..10 {
  // ...
}
  • 示例 2
enum Direction {
    East,
    West,
    North,
    South,
}

fn main() {
    let dire = Direction::South;
    match dire {
        Direction::East => println!("East"),
        Direction::North | Direction::South => {
            println!("South or North");
        },
        _ => println!("West"),
    };
}

12.4 忽略符..

.. 可以出现在序列(Range)中,也可以用于忽略匹配值,忽略规则如下: 1. 忽略前值或后值 2. 忽略中间值 3. .. 必须是无歧义的 - 忽略前、后值:

#![allow(unused)]
fn main() {
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),
}
}
  • 忽略中间值:
fn main() {
    let numbers = (2, 4, 8, 16, 32);

    match numbers {
        (first, .., last) => {
            println!("Some numbers: {}, {}", first, last);
        },
    }
}
  • 有歧义的,编译失败:
fn main() {
    let numbers = (2, 4, 8, 16, 32);

    match numbers {
        (.., second, ..) => {
            println!("Some numbers: {}", second)
        },
    }
}

12.5 Result 和?

? 是一种针对 Result 类型做 match 操作的语法糖。如果结果是 Ok(T),则把 T 赋值给 f,如果结果是 Err(E),则返回该错误,所以 ? 特别适合用来传播错误。

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

fn read_username_from_file() -> Result<String, io::Error> {
    // 打开文件,f是`Result<文件句柄,io::Error>`
    let f = File::open("hello.txt");

    let mut f = match f {
        // 打开文件成功,将file句柄赋值给f
        Ok(file) => file,
        // 打开文件失败,将错误返回(向上传播)
        Err(e) => return Err(e),
    };
    // 创建动态字符串s
    let mut s = String::new();
    // 从f文件句柄读取数据并写入s中
    match f.read_to_string(&mut s) {
        // 读取成功,返回Ok封装的字符串
        Ok(_) => Ok(s),
        // 将错误向上传播
        Err(e) => Err(e),
    }
}

上面的可以用? 替代。

#![allow(unused)]
fn main() {
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)
}
}

12.5.1 try!

在当前版本中,我们要尽量避免使用 try!。 在 ? 横空出世之前 ( Rust 1.13 ),Rust 开发者还可以使用 try! 来处理错误,该宏的大致定义如下:

macro_rules! try {
    ($e:expr) => (match $e {
        Ok(val) => val,
        Err(err) => return Err(::std::convert::From::from(err)),
    });
}

简单看一下与 ? 的对比:

//  `?`
let x = function_with_error()?; // 若返回 Err, 则立刻返回;若返回 Ok(255),则将 x 的值设置为 255

// `try!()`
let x = try!(function_with_error());

可以看出 ? 的优势非常明显,何况 ? 还能做链式调用。

fn last_char_of_first_line(text: &str) -> Option<char> {
    text.lines().next()?.chars().last()
}

13 特殊语法

13.1 !Trait

!Trait 表示是不具有特征约束,和 Trait 用法一样。 以标准库 Send 特征为例,*const T*mut T 被定义为 !Send,即不具有 Send 特征约束的;而具有 T: Sync 特征的 &T 被定义为具有 Send 特征约束的。

#[stable(feature = "rust1", since = "1.0.0")]
#[cfg_attr(not(test), rustc_diagnostic_item = "Send")]
#[rustc_on_unimplemented(
    message = "`{Self}` cannot be sent between threads safely",
    label = "`{Self}` cannot be sent between threads safely"
)]
pub unsafe auto trait Send {
    // empty.
}

#[stable(feature = "rust1", since = "1.0.0")]
impl<T: ?Sized> !Send for *const T {}
#[stable(feature = "rust1", since = "1.0.0")]
impl<T: ?Sized> !Send for *mut T {}

// 大多数实例会自动出现,但需要此实例将 `T: Sync` 与 `&T: Send` 链接起来 (并且它还删除了不合理的默认实例 `T Send` -> `&T: Send` 否则会存在)。
//
//
#[stable(feature = "rust1", since = "1.0.0")]
unsafe impl<T: Sync + ?Sized> Send for &T {}

13.2 *T

Rust中在声明引用和指针类型和C++中很像,都是`&`表示引用,`*`表示指针,但不同的是rust中`&*`都是在类型前面`&T`、`*T`,而C++中是在类型后面`T&`、`T*`。

*T 是一个指针类型的写法,其中 T 是指针所指向的类型。这种写法通常被称为解引用操作符。

fn main() {
    let x: i32 = 42;
    let ptr: *const i32 = &x;

    // 使用 *ptr 解引用指针,得到指针指向的值 (注意:访问指针是不安全的)
    unsafe {
        let value: i32 = *ptr;
        println!("The value is: {}", value);
    }
}

上面的 &x 表示取 x 的地址,不是指引用 x,这点和 C++ 中一样。*ptr 同理表示的是指针解引用,同样和 C++ 中一样。访问指针是不安全的,因此需要 unsafe 块。