Rust的问题与解答(二)

23 次浏览
2024年07月23日创建

关于&引用使用的时机

我的总结:如果是一些基本类型能够自动copy的,那么这种数据作为函数参数的时候,可以不用考虑&,并且在函数执行完后,仍然能够正常使用,如果参数不是copy类型,那么在函数调用完后想继续使用,或者copy特别耗性能,就使用&。

在 Rust 中,对于一些实现了 Copy trait 的基本类型,可以直接传递值而不需要担心所有权转移的问题。这些类型在传递给函数时会自动复制,函数内部和外部都可以独立地使用这些值。

Rust 中的基本类型(如整数、浮点数、布尔值、字符等)和一些简单的复合类型(如固定大小的数组和元组)默认实现了 Copy trait。这意味着当它们被传递给函数时,会进行按位复制(bitwise copy),而不会转移所有权。

对于非 Copy 类型(如 String、Vec 等),传递值会转移所有权,函数调用后原来的变量将不可用。如果你需要在函数调用后继续使用原来的数据,可以使用引用。

大数据结构,即使它们实现了 Copy trait,复制的开销也可能很大。在这种情况下,使用引用可以避免不必要的复制,提高性能。

函数嵌套调用中的 ? 操作符

Rust 中的 ? 操作符是用于简化错误处理的,它可以在函数返回 Result 或 Option 类型时,将错误自动传播给调用者。当遇到错误时,? 操作符会提前返回错误,而不会继续执行后续代码。

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

// 读取文件内容
fn read_file_content(file_path: &str) -> Result<String, io::Error> {
    let mut file = File::open(file_path)?;
    let mut content = String::new();
    file.read_to_string(&mut content)?;
    Ok(content)
}

// 解析字符串中的数字
fn parse_number(content: &str) -> Result<i32, ParseIntError> {
    let number: i32 = content.trim().parse()?;
    Ok(number)
}

// 读取文件并解析数字
fn read_and_parse(file_path: &str) -> Result<i32, Box<dyn std::error::Error>> {
    let content = read_file_content(file_path)?;
    let number = parse_number(&content)?;
    Ok(number)
}

fn main() {
    match read_and_parse("example.txt") {
        Ok(number) => println!("The number is: {}", number),
        Err(e) => eprintln!("Error: {}", e),
    }
}

如果想要捕获的话,就去掉?通过match来匹配是Ok还是Err。

可以取切片的一部分引用

fn print(n: &[f64]) {
    for elt in n {
        println!("{}", elt);
    }
}

#[test]
pub fn print_test(){
    let v: Vec<f64> = vec![0.0,  0.707,  1.0,  0.707];
    let a: [f64; 4] =     [0.0, -0.707, -1.0, -0.707];

    print(&v[0..2]);
    print(&a[2..]);
}

String可以数组切片访问

let mut s = "hello";
s[0] = 'c';

vec在堆中访问,string也在堆中,但是数组在栈里。

let ab = "good";
let ad = &ab;
let ae = "good".to_string();
let af = &ae;
println!("{}", ab);
println!("{:p}", ad);
println!("{}", ae);
println!("{:p}", af);

字符串String、数组[T]、Vec<T>还有 &str、&[T]与&vec之间的关系

在 Rust 中,字符串和数组有多种形式,每种形式在内存管理和使用方式上都有所不同。以下是对 `String`、数组 `[T]`、`Vec<T>` 以及引用类型 `&str`、`&[T]` 和 `&Vec<T>` 之间关系的详细解释:

String

- `String` 是一个可变的、拥有所有权的字符串类型,存储在堆上。

- 它是动态分配的,可以根据需要增长。

- 适用于需要修改字符串内容或动态生成字符串的场景。

&str

- `&str` 是一个不可变的字符串切片,通常指向二进制数据中的一部分。

- 它可以指向字符串字面值(如 `"hello"`)或 `String` 的一部分。

- 适用于只读字符串数据,不需要修改内容的场景。

数组 [T; N]

- 数组是固定大小的,存储在栈上。

- `[T; N]` 表示一个包含 `N` 个元素的数组,每个元素的类型为 `T`。

- 适用于已知大小的数据集合。

切片 &[T]

- `&[T]` 是一个不可变的数组切片,可以引用数组或向量的一部分。

- 切片不拥有数据,只是对数据的一个视图。

- 适用于需要只读访问数组或向量部分数据的场景。

向量 Vec<T>

- `Vec<T>` 是一个可变的、拥有所有权的动态数组类型,存储在堆上。

- 它可以根据需要增长或缩减。

- 适用于需要动态调整大小的数据集合。

引用 &Vec<T>

- `&Vec<T>` 是一个对向量的不可变引用。

- 它不拥有数据,只是对向量的一个视图。

- 适用于需要只读访问整个向量的场景。

关系和转换

1. String和 &str

`String` 可以通过 `as_str()` 方法转换为 `&str`。

可以通过 `&` 操作符将 `String` 转换为 `&str`。

let s = String::from("hello");
let s_slice: &str = &s;  // 转换为 &str

2. 数组 `[T; N]` 和切片 `&[T]`

数组可以通过 `&` 操作符转换为切片。

切片是对数组的一部分或全部的引用。

let arr = [1, 2, 3, 4];
let slice: &[i32] = &arr;  // 转换为 &[i32]

3. `Vec<T>` 和切片 `&[T]`

Vec<T>` 可以通过 `as_slice()` 方法转换为切片。

可以通过 `&` 操作符将 `Vec<T>` 转换为切片。

let v = vec![1, 2, 3, 4];
let slice: &[i32] = &v;  // 转换为 &[i32]

4. Vec<T> 和 &Vec<T>

Vec<T>可以通过 & 操作符转换为 &Vec<T>。

let v = vec![1, 2, 3, 4];
let v_ref: &Vec<i32> = &v;  // 转换为 &Vec<i32>

示例代码

fn main() {
    // String 和 &str
    let s = String::from("hello");
    let s_slice: &str = &s;  // String 转换为 &str

    // 数组和切片
    let arr = [1, 2, 3, 4];
    let arr_slice: &[i32] = &arr;  // 数组转换为切片

    // Vec 和切片
    let v = vec![1, 2, 3, 4];
    let v_slice: &[i32] = &v;  // Vec 转换为切片

    // Vec 和 &Vec
    let v_ref: &Vec<i32> = &v;  // Vec 转换为 &Vec
}

总结

  • String 和 &str 用于字符串操作,前者可变且拥有所有权,后者不可变且只读。
  • 数组 [T; N]和切片 &[T] 用于固定大小的数据集合,前者存储在栈上,后者是对数组的引用。
  • Vec<T>和&Vec<T>用于动态大小的数据集合,前者可变且拥有所有权,后者是对向量的引用。

String怎么动态增长

以下是一些常用的方法,用于向 String 添加数据,从而触发动态增长:

  • push_str 方法:将一个字符串切片追加到 String 末尾。
  • push 方法:将一个字符追加到 String 末尾。
  • + 运算符和 format! 宏:用于连接字符串。
let mut ah = String::from("good");
ah.push_str(" job");

let aj = " for me";
ah = ah + &aj;
print!("{}", ah);

关于字符串的使用方式

Rust 的解决方案是为这些情况提供一些类似字符串的类型。

  • 对于 Unicode 文本,坚持使用 String 和 &str。
  • 当使用文件名时,请改用 std::path::PathBuf 和 &Path。
  • 当处理根本不是 UTF-8 编码的二进制数据时,请使用 Vec<u8> 和 &[u8]。
  • 当使用操作系统提供的原生形式的环境变量名和命令行参数时, 请使用 OsString 和 &OsStr。
  • 当和使用 null 结尾字符串的 C 语言库进行互操作时,请使用 std::ffi::CString 和 &CStr。

哪些不能是Copy类型

任何在丢弃值时需要做一些特殊操作的类型都不能是 Copy 类型:Vec 需要释放自身元素、File 需要关闭自身文件句 柄、MutexGuard 需要解锁自身互斥锁,等等。对这些类型进行逐位 复制会让我们无法弄清哪个值该对原始资源负责。

但是编译器不能自己判断是Copy类型,比如:

struct Person{
    age: u32
}

虽然Person只有一个u32,但仍然不是Copy类型,需要手动声明:

#[derive(Copy)]
struct Person{
    age: u32
}

但这样有问题,如果一个结构体字段不全是Copy类型,则无法这样做:

至于为什么符合条件的用户定义类型不能自动成为 Copy 类型呢?

这是因为类型是否为 Copy 对于在代码中使用它的方式有着重大影响:Copy 类型更灵活,因为赋值和相关操作不会把原始值变成未初始化状态。 但对类型的实现者而言,情况恰恰相反:Copy 类型可以包含的类型 非常有限,而非 Copy 类型可以在堆上分配内存并拥有其他种类的资 源。因此,创建一个 Copy 类型代表着实现者的郑重承诺:如果以后 确有必要将其改为非 Copy 类型,则使用它的大部分代码可能需要进 行调整。

vec的clone是深拷贝,如果vec内的item是引用,那么copy的也是引用,不会递归clone,而Rc引用直接就是copy的引用。

let s: Rc<String> = Rc::new("shirataki".to_string());
let t: Rc<String> = s.clone();
let u: Rc<String> = s.clone();

引用的类型

共享引用是 Copy 类型。可变引用不是 Copy 类型。只要存在对一个值的共享引 用,即使是它的拥有者也不能修改它,该值会被锁定。在 Rust 中 要使用 & 运算符和 * 运算符来创建引用(借用)和追踪引用(解引 用),不过 . 运算符不需要做这种转换,它会隐式借用和解引用。

判断引用是否相等

assert!(rx == ry);
assert!(!std::ptr::eq(rx, ry));

Rust 允许借用任意种类的表达式结果值的引用:

fn factorial(n: usize) -> usize {
    (1..n+1).product()
}
let r = &factorial(6);
// 数学运算符可以“看穿”一层引用 assert_eq!(r + &1009, 1729);

&的生命周期

  • 如果方法入参一个引用,但没有返回值:
pub fn test_lifetime <'static> (p: &'static i32){

}

这个其实没啥好说的,约束了p引用不能是指向'a的生命周期的引用,因为没有返回值,所以不用约束返回值的生命周期一致。假如代码:

let i = 3;
test_lifetime(&i);

这段代码会报错。

  • 携带返回值
pub fn test_lifetime_response <'a> (p: &'a i32) -> &'a i32{
    &3
}

这个时候不仅约束了p的生命周期,还有返回值的生命周期也得和p一致。假如代码:

let zz; {
    let zw = 3;
    zz = test_lifetime_response(&zw);
}
println!("{}", *zz);

这样的代码报错,因为入参数的p和返回值的生命周期不一致。

  • 结构体包含引用

每当一个引用类型出现在另一个类型的定义中时,必须写出它的生命周期。

struct S<'a, 'b> {
    r1: &'a i32,
    r2: &'b i32,
}

fn create_s<'a, 'b>(x: &'a i32, y: &'b i32) -> S<'a, 'b> {
    S { r1: x, r2: y }
}

let value1 = 10;
let result;
{
    let value2 = 20;
    let s = S{r1:&value1, r2:&value2};
    result = s.r1;
}
println!("result {}", *result);

上面的代码能够通过运行,是因为入参数a、b的生命周期不同,如果再套一层也会报错,因为value1的生命周期已经结束:

let result;
{
    let value1 = 10;
    {
        let value2 = 20;
        let s = S{r1:&value1, r2:&value2};
        result = s.r1;
    }
}
println!("result {}", *result);

如果改成相同生命周期同样会报错,因为r2的生命周期和r1并不相同:

struct S<'a> {
    r1: &'a i32,
    r2: &'a i32,
}

fn create_s<'a, 'b>(x: &'a i32, y: &'a i32) -> S<'a> {
    S { r1: x, r2: y }
}

Rust 中的每个类型都有生 命周期,包括 i32 和 String。它们大多数是 'static 的,这意 味着这些类型的值可以一直存续下去,例如,Vec<i32> 是自包含 的,在任何特定变量超出作用域之前都不需要丢弃它。但是像 Vec<&'a i32> 这样的类型,其生命周期就必须被 'a 涵盖,也就 是说必须在引用目标仍然存续的情况下丢弃它。

共享与可变引用的传递

let mut x = 10;
let r1 = &x;
let r2 = &x; // 正确:允许多个共享借用
x += 10; // 错误:不能赋值给`x`,因为它已被借出
let m = &mut x;

let mut y = 20;
let m1 = &mut y;
let m2 = &mut y; // 错误:不能多次借入为可变引用
let z = y; // 错误:不能使用`y`,因为它涵盖在已借出的可变引用的生命周 期内
println!("{}, {}, {}", m1, m2, z); // 在这里使用这些引用

let mut w = (107, 109);
let r = &w;
let r0 = &r.0;// 正确:把共享引用重新借入为共享引用
//let m1 = &mut r.1;// 错误:不能把共享引用重新借入为可变
println!("{}", r0);

let mut v = (136, 139);
let m = &mut v;
let m0 = &mut m.0; // 正确: 从可变引用中借入可变引用
*m0 = 137;
let r1 = &m.1;// 正确: 从可变引用中借入共享引用,并且不能和m0重
v.1; // 错误:禁止通过其他路径访问 
println!("{}", r1);// 可以在这里使用r1

结构体默认情况下是私有的,仅在声明它们 的模块及其子模块中可见。对于方法调用和字段访问,Rust 会自动从 Box、Rc、Arc 等指针类 型中借入引用,因此 &self 和 &mut self 几乎总是(偶尔也会用 一下 self)方法签名里的正确选择。

Self类型

//TODO

生命周期的范型结构体

一个生命周期的范型结构体

struct MyStruct<'a, T> {
    value: &'a T,
}

带多个生命周期的范型结构体:

struct MultipleRefs<'a, 'b, T> {
    first: &'a T,
    second: &'b T,
}

impl<'a, 'b, T> MultipleRefs<'a, 'b, T> {
    fn new(first: &'a T, second: &'b T) -> Self {
        MultipleRefs { first, second }
    }

    fn get_first(&self) -> &'a T {
        self.first
    }

    fn get_second(&self) -> &'b T {
        self.second
    }
}

fn main() {
    let x = 10;
    let y = 20;
    let refs = MultipleRefs::new(&x, &y);
    println!("First: {}", refs.get_first());
    println!("Second: {}", refs.get_second());
}

给结构体加trait

与结构体一样,编译器能为你实现 == 运算符等特性,但你必须明确 提出要求

#[derive(Copy, Clone, Debug, PartialEq)] enum RoughTime {
    InThePast(TimeUnit, u32),
    JustNow,
    InTheFuture(TimeUnit, u32),
}

match n、other、_。元组也可以、slice也可以,结构体也行:

match account {
    Account { ref name, ref language, .. } => {
        ui.greet(name, language);
        ui.show_settings(&account); // 正确 
    }
}

可变引用也行:

match line_result {
    Err(ref err) => log_error(err),
    Ok(ref mut line) => {
        trim_comments(line);
        handle(line);
    }
}

这特么也行

match sphere.center() {
    &Point3d { x, y, z } => ...
}

在 Rust 中,`match` 表达式可以用于匹配引用类型。这在处理借用的数据时非常有用。下面是一些常见的场景和示例,展示如何在 `match` 表达式中匹配引用。

### 1. 匹配引用类型

假设我们有一个整数的引用,我们可以使用 `match` 来匹配它:

```rust

fn main() {
    let x = 5;
    let y = &x;

    match y {
        &val => println!("Got a value: {}", val),
    }
}

```

在这个例子中,`y` 是一个对 `x` 的引用。我们在 `match` 表达式中使用 `&val` 模式来解引用 `y` 并将其值绑定到 `val`。

### 2. 使用 `ref` 关键字匹配引用

在某些情况下,你可能希望保留引用而不是解引用。这时可以使用 `ref` 关键字:

```rust

fn main() {
    let x = 5;
    let y = &x;

    match y {
        ref val => println!("Got a reference to value: {}", val),
    }
}

```

在这个例子中,我们使用 `ref val` 模式来匹配引用并将其绑定到 `val`,这样我们就可以继续使用引用而不是解引用后的值。

### 3. 匹配可变引用

如果你有一个可变引用,可以使用 `ref mut` 关键字来匹配它:

```rust

fn main() {
    let mut x = 5;
    let y = &mut x;

    match y {
        ref mut val => {
            **val += 1;
            println!("Got a mutable reference to value: {}", val);
        },
    }
}

```

在这个例子中,我们使用 `ref mut val` 模式来匹配可变引用并将其绑定到 `val`。然后我们可以通过 `*val` 来修改引用指向的值。

### 4. 在结构体中匹配引用

如果你有一个包含引用的结构体,可以在 `match` 表达式中匹配这些引用:

```rust

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

fn main() {
    let point = Point { x: 10, y: 20 };
    let r = &point;

    match r {
        &Point { x, y } => println!("Point has coordinates: ({}, {})", x, y),
    }
}

```

在这个例子中,我们匹配一个包含引用的结构体 `Point`。通过 `&Point { x, y }` 模式,我们可以解引用 `r` 并将其字段绑定到 `x` 和 `y`。

### 5. 在枚举中匹配引用

如果你有一个包含引用的枚举,可以在 `match` 表达式中匹配这些引用:

```rust

enum Message {
    Hello { id: i32 },
}

fn main() {
    let msg = Message::Hello { id: 5 };

let r = &msg;


    match r {
        &Message::Hello { id } => println!("Hello message with id: {}", id),
    }
}

```

在这个例子中,我们匹配一个包含引用的枚举 `Message`。通过 `&Message::Hello { id }` 模式,我们可以解引用 `r` 并将其字段绑定到 `id`。

### 总结

在 Rust 中,`match` 表达式可以灵活地用于匹配引用类型。你可以使用 `&`、`ref` 和 `ref mut` 关键字来匹配和处理引用。无论是简单的引用,还是结构体和枚举中的引用,`match` 表达式都提供了强大的模式匹配能力,使得代码更加清晰和易于维护。

fn a(click: i32, current_hex:i32) -> Result<i32, Err>{
    match point_to_hex(click) {
        None => Err("That's not a game space."),
        Some(hex) if hex == current_hex =>
            Err("You are already there! You must click somewhereelse"),
        Some(hex) => Ok(hex)
    }
}

Some(hex) if hex == current_hex