Rust圣经(一)

17 次浏览
2024年07月29日创建

栈上分配固定大小,性能高。堆中分配随意大小,但耗费性能。

引用与借用的区别,什么情况下会发生栈上复制?不会产生借用。

字符串字面量和基本类型都是复制,指针也是。

深拷贝Clone与浅拷贝Copy,

什么类型可以浅拷贝Copy?

  • 所有整数类型,比如 u32
  • 布尔类型,bool,它的值是 true 和 false
  • 所有浮点数类型,比如 f64
  • 字符类型,char、
  • 元组,当且仅当其包含的类型也都是 Copy 的时候。比如,(i32, i32) 是 Copy 的,但 (i32, String) 就不是
  • 不可变引用 &T ,例如转移所有权中的最后一个例子,但是注意: 可变引用 &mut T 是不可以 Copy的

函数入参数和返回值会发生转移。

编译器对作用域的优化

悬垂引用

默认情况下,非 Copy 类型的赋值和函数传递会导致所有权转移。

实现了 Copy trait 的类型赋值和函数传递会进行值的复制。

是的,你的理解是正确的。以下是具体的类型和它们的行为总结:

### 实现了 `Copy` Trait 的类型

1. **所有整数类型**:

- 例如:`u8`, `u16`, `u32`, `u64`, `u128`, `i8`, `i16`, `i32`, `i64`, `i128`

2. **布尔类型**:

- `bool`,它的值是 `true` 和 `false`

3. **所有浮点数类型**:

- 例如:`f32`, `f64`

4. **字符类型**:

- `char`

5. **元组**:

- 元组类型当且仅当其包含的所有类型也都是 `Copy` 类型时,它才是 `Copy` 的。例如:

- `(i32, i32)` 是 `Copy` 的,因为 `i32` 是 `Copy` 的。

- `(i32, String)` 不是 `Copy` 的,因为 `String` 不是 `Copy` 的。

6. **不可变引用**:

- 不可变引用 `&T` 是 `Copy` 的,因为引用本身是一个指针,它的复制是浅拷贝。例如:

```rust

     let x = 5;
     let y = &x;
     let z = y; // y 和 z 都是 &i32 类型的不可变引用

```

### 不实现 `Copy` Trait 的类型

1. **可变引用**:

- 可变引用 `&mut T` 不是 `Copy` 的,因为可变引用的复制会导致数据竞争问题。例如:

```rust

     let mut x = 5;
     let y = &mut x;
     // let z = y; // 这行代码会报错,因为 &mut T 不是 `Copy` 的

```

2. **动态分配的类型**:

- 例如:`String`, `Vec<T>`, `Box<T>` 等,它们在堆上分配内存,默认情况下不实现 `Copy`。

### 示例代码说明

以下是一些示例代码,展示了不同类型的赋值和所有权转移行为:

```rust

fn main() {
    // 整数类型
    let a = 10;
    let b = a; // 直接复制,a 仍然有效
    println!("a = {}, b = {}", a, b);

    // 布尔类型
    let c = true;
    let d = c; // 直接复制,c 仍然有效
    println!("c = {}, d = {}", c, d);

    // 浮点数类型
    let e = 3.14;
    let f = e; // 直接复制,e 仍然有效
    println!("e = {}, f = {}", e, f);

    // 字符类型
    let g = 'a';
    let h = g; // 直接复制,g 仍然有效
    println!("g = {}, h = {}", g, h);

    // 元组类型
    let i = (10, 20);
    let j = i; // 直接复制,i 仍然有效
    println!("i = ({}, {}), j = ({}, {})", i.0, i.1, j.0, j.1);

    // 不可变引用
    let k = 42;
    let l = &k; // 不可变引用,直接复制,k 仍然有效
    let m = l; // l 和 m 都是 &i32 类型的不可变引用
    println!("k = {}, l = {}, m = {}", k, l, m);

    // 可变引用
    let mut n = 100;
    let o = &mut n;
    // let p = o; // 这行代码会报错,因为 &mut T 不是 `Copy` 的
    println!("o = {}", o);
}

```

### 总结

- **实现了 `Copy` Trait 的类型**:赋值操作会进行值的复制,原变量仍然有效。

- **不实现 `Copy` Trait 的类型**:赋值操作会导致所有权转移,原变量不再有效。

- **不可变引用**:是 `Copy` 的,可以安全地复制。

- **可变引用**:不是 `Copy` 的,复制会导致数据竞争问题。

&str 是一个不可变引用类型,而不可变引用类型是实现了 Copy trait 的。因此,&str 类型的变量是可以被复制的。

String与&str

fn main() {
    let s = String::from("hello,world!");
    say_hello(&s);
    say_hello(&s[..]);
    say_hello(s.as_str());
}

fn say_hello(s: &str) {
    println!("{}",s);
}

String::from("hello,world")
"hello,world".to_string()

必须要将结构体实例声明为可变的,才能修改其中的字段,Rust 不支持将某个结构体某个字段标记为可变。

当函数参数和结构体字段同名时,可以直接使用缩略的方式进行初始化,跟 TypeScript 中一模一样。

 let user2 = User {
        email: String::from("another@example.com"),
        ..user1
    };

结构体赋值可能会导致所有权转移。

元组结构体在你希望有一个整体名称,但是又不关心里面字段的名称时将非常有用。

dbg!(&rect1); 输出控制台信息,包含文件名、行号、表达式以及表达式的值。

通过 :: 操作符来访问 枚举 下的具体成员。

如果想在循环中,修改该元素,可以使用 mut 关键字:

for item in &mut collection {
  // ...
}

同时获取索引

   let a = [4, 3, 2, 1];
    // `.iter()` 方法把 `a` 数组变成一个迭代器
    for (i, v) in a.iter().enumerate() {
        println!("第{}个元素是{}", i + 1, v);
    }

当使用 loop 时,必不可少的伙伴是 break 关键字,它能让循环在满足某个条件时跳出:

fn main() {
    let mut counter = 0;

    let result = loop {
        counter += 1;

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

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

match还可以用 |

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

以及

v.iter().filter(|x| matches!(x, MyEnum::Foo));

if let 往往用于匹配一个模式,而忽略剩下的所有模式的场景

一个与 if let 类似的结构是 while let 条件循环,它允许只要模式匹配就一直进行 while 循环。

枚举内可以不同结构:

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

let ((feet, inches), Point {x, y}) = ((3, 10), Point { x: 3, y: -10 });

println!("feet: {}", feet);
println!("inches: {}", inches);
println!("x: {}", x);
println!("y: {}", y);

同时匹配:

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

...省略

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

匹配守卫


match num {
    Some(x) if x < 5 => println!("less than five: {}", x),
    Some(x) => println!("{}", x),
    None => (),
}

这特么也行:这个匹配条件表明此分支只匹配 x 值为 4、5 或 6 同时 y 为 true 的情况。

let x = 4;
let y = false;

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

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

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

绑定新#[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 :(");
    }
}

定义在 impl 中且没有 self 的函数被称之为关联函数。

:: 语法用于关联函数和模块创建的命名空间。

fn display_array<T: std::fmt::Debug, const N: usize>(arr: [T; N]) {
    println!("{:?}", arr);
}
fn main() {
    let arr: [i32; 3] = [1, 2, 3];
    display_array(arr);

    let arr: [i32; 2] = [1, 2];
    display_array(arr);
}

特征作为函数参数

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

在简单的场景下 impl Trait 这种语法糖就足够使用,但是对于复杂的场景,特征约束可以让我们拥有更大的灵活性和语法表现能力,

如果函数两个参数是不同的类型,那么上面的方法很好,只要这两个类型都实现了 Summary 特征即可。但是如果我们想要强制函数的两个参数是同一类型呢?上面的语法就无法做到这种限制,此时我们只能使特征约束来实现:

pub fn notify(item1: &impl Summary, item2: &impl Summary) {}

泛型类型 T 说明了 item1 和 item2 必须拥有同样的类型,同时 T: Summary 说明了 T 必须实现 Summary 特征。

可以通过 impl Trait 来说明一个函数返回了一个类型,该类型实现了某个特征:

fn returns_summarizable() -> impl Summary {
    Weibo {
        username: String::from("sunface"),
        content: String::from(
            "m1 max太厉害了,电脑再也不会卡",
        )
    }
}

在本书中,形如 #[derive(Debug)] 的代码已经出现了很多次,这种是一种特征派生语法,被 derive 标记的对象会自动实现对应的默认特征代码,继承相应的功能。

在一些场景中,使用 as 关键字做类型转换会有比较大的限制,因为你想要在类型转换上拥有完全的控制,例如处理转换错误,那么你将需要 TryInto:

use std::convert::TryInto;

fn main() {
  let a: i32 = 10;
  let b: u16 = 100;

  let b_ = b.try_into()
            .unwrap();

  if a < b_ {
    println!("Ten is less than one hundred.");
  }
}

在 Rust 中,`dyn Draw`、`impl Draw` 和 `: Draw` 用于不同的场景,主要涉及到动态分发和静态分发的选择,以及泛型约束。让我们详细讨论一下它们的使用场景。

### 1. `dyn Draw`

`dyn Draw` 用于动态分发,也就是运行时多态。它通常用于需要在运行时确定具体类型的场合,例如在需要存储不同类型但实现了相同 trait 的对象时。

**使用场景**:

- 当你需要在集合中存储不同类型的对象,但这些对象都实现了相同的 trait。

- 当你需要通过 trait 对象来实现动态分发,以便在运行时选择具体的实现。

**示例**:

```rust

trait Draw {

fn draw(&self);

}

struct Circle;

struct Square;

impl Draw for Circle {

fn draw(&self) {

println!("Drawing a circle");

}

}

impl Draw for Square {

fn draw(&self) {

println!("Drawing a square");

}

}

fn main() {

let shapes: Vec<Box<dyn Draw>> = vec![Box::new(Circle), Box::new(Square)];

for shape in shapes {

shape.draw();

}

}

```

在这个例子中,`Vec<Box<dyn Draw>>` 可以存储实现了 `Draw` trait 的不同类型的对象,并在运行时调用它们的 `draw` 方法。

### 2. `impl Draw`

`impl Draw` 用于静态分发,也就是编译时多态。它通常用于泛型函数或返回值类型是具体实现了某个 trait 的类型,但不需要在运行时确定具体类型。

**使用场景**:

- 当你编写泛型函数并希望参数或返回值是实现了某个 trait 的具体类型。

- 当你希望通过静态分发来获得更高的性能,因为所有的类型信息在编译时就已经确定。

**示例**:

```rust

trait Draw {

fn draw(&self);

}

struct Circle;

struct Square;

impl Draw for Circle {

fn draw(&self) {

println!("Drawing a circle");

}

}

impl Draw for Square {

fn draw(&self) {

println!("Drawing a square");

}

}

fn draw_shape(shape: impl Draw) {

shape.draw();

}

fn main() {

let circle = Circle;

let square = Square;

draw_shape(circle);

draw_shape(square);

}

```

在这个例子中,`draw_shape` 函数接受任何实现了 `Draw` trait 的类型,并在编译时确定具体的类型。

### 3. `: Draw`

`: Draw` 用于泛型约束。当你编写泛型代码时,需要确保泛型类型参数实现了某个 trait。

**使用场景**:

- 当你编写泛型数据结构或函数,并希望泛型参数实现某个 trait。

- 当你希望通过泛型约束来确保类型安全和接口一致性。

**示例**:

```rust

trait Draw {

fn draw(&self);

}

struct Circle;

struct Square;

impl Draw for Circle {

fn draw(&self) {

println!("Drawing a circle");

}

}

impl Draw for Square {

fn draw(&self) {

println!("Drawing a square");

}

}

fn draw_shape<T: Draw>(shape: T) {

shape.draw();

}

fn main() {

let circle = Circle;

let square = Square;

draw_shape(circle);

draw_shape(square);

}

```

在这个例子中,`draw_shape` 函数使用泛型参数 `T`,并通过 `T: Draw` 约束确保 `T` 实现了 `Draw` trait。

### 总结

- **`dyn Draw`**:用于动态分发,在运行时确定具体类型,常用于需要存储不同类型的对象的场合。

- **`impl Draw`**:用于静态分发,在编译时确定具体类型,常用于泛型函数或返回值类型是具体实现了某个 trait 的类型的场合。

- **`: Draw`**:用于泛型约束,确保泛型类型参数实现了某个 trait,常用于泛型数据结构或函数的场合。

根据具体需求选择合适的方式,可以在性能和灵活性之间取得平衡。