Skip to content

一些基础概念

Turbo-Fish

Turbo-Fish 是 Rust 语言的一个自定义语法扩展,在泛型函数或方法调用时,显式指定泛型类型参数的一种语法规则,使用双冒号(::)和尖括号(<>)组合在一起,例如:

rust
let x = "hello".to_string();
// 使用泛型类型
let y = Vec::<i32>::new();

这种写法被称为Turbo-Fish,因为它看起来像一条鱼的轮廓(::<>)。这种语法通常用于帮助编译器确定泛型类型或避免二义性。

元组无法迭代

元组(Tuple)是不同类型的值的固定大小集合,也称为异构(Heterogeneous)。而 Rust 的迭代器实现通常被认为是同态的(Homogeneous),即它们针对同一种类型的元素进行迭代。

根据 Rust 标准库文档,标准库中的 std::iter::Iterator trait 可以用于定义一种迭代器,该迭代器可以按顺序遍历某个数据结构中的所有元素,并且具有类似指针的特性。因此,在处理一个含有多种类型元素的元组时,由于不同类型元素所占字节、排列方式等不同,导致统一的迭代器遍历起来具有很大的难度和不确定性。

  • 什么场景下会用到元组这种数据结构呢?

函数要返回多个变量时,调用返回时直接解构,很方便 比如 返回(res, err)这类。

  • 元组为什么不通过[]访问而是通过.呢?

区别于数组。并且本身跟数组有区别,数组元素类型一致,意味着存储空间大小一致,移动指针容易计算。元组各元素类型不一致,访问时指针位置要计算。

rust 中复杂类型都是存储在堆上吗?

Rust 中的复杂类型可以存储在堆上或栈上,具体取决于该类型的大小和其生命周期。

当变量的大小在编译时是已知并且不超过一个固定大小(通常为 16 或 32 字节)时,它们会被分配在栈上。例如,整数、浮点数和较小的结构体都可以在栈上进行分配,这通常比在堆上分配要更快并且不需要执行显式内存管理操作(如清理)。

然而,如果变量的大小在编译时未知或可能超过固定大小,则需要使用堆来分配内存。例如,字符串、向量和其他动态类型都使用堆来存储数据。这些类型的大小不能在编译时确定,并且它们的生命周期可能超出当前函数作用域。

此外,Rust 还支持将某些复杂类型存储在栈上或堆上。例如,在 Rust 中,结构体可以包含堆分配和栈分配的字段,这意味着结构体可能会很大,并且只有一部分内存被存储在堆上。

综上所述,Rust 中的复杂类型可以存储在栈上或堆上。根据类型的大小和生命周期,Rust 编译器在编译时决定将其分配到哪个区域。

#![...]#[...]区别

在 Rust 中,#![...]#[...] 都是属性宏的使用方式,但它们的作用范围不同。

#![...] 是一个 crate 属性,它用于设置整个 crate (包含多个模块的 Rust 项目)的元数据信息、编译选项和其他一些指令。这些属性会被编译器读取并对整个 crate 进行配置,而不仅仅是某个单独的模块或函数。

rust
#![allow(dead_code)]
#![allow(unused_variables)]
#![feature(proc_macro_hygiene)]

举个例子,以下代码片段展示了如何使用 #![feature(some_feature)] 设置一个编译时特性,以启用某些实验性的 Rust 特性:

rust
#![feature(some_feature)]

fn main() {
    // 使用实验性特性...
}

在上述代码中,#![feature(some_feature)] 声明了一个特性,让编译器知道这个 crate 中使用了某些实验性特性,并在编译时将其启用。这个特性会影响整个 crate的编译过程,而不仅仅是 main 函数。 通常将这些属性写在项目的根模块(src/lib.rssrc/main.rs)的开头。

#[...]是一个 item 级别的属性,它只影响当前定义的函数、结构体、枚举、trait 等实体。这些属性同样可以用于控制编译器的行为或者告诉编译器一些信息,例如:

rust
#[derive(Debug)]
pub struct Person {
    name: String,
    age: u8,
}

fn main() {
    let student = Person {
        name: "ChandlerVer5".to_string(),
        age: 31,
    };
    println!("{:?}", student); // 输出 Person { name: "ChandlerVer5", age: 31 }
}

这里的 #[derive(Debug)] 属性告诉编译器为 Person 结构体实现 Debug trait,允许我们使用 println! 宏输出结构体的内容。

总之,#![...]用于给整个 crate 设置属性,#[...]用于给一个 Rust 项设置属性。

Orphan Rule(孤儿规则)

Rust 规定了一个 Coherence Rule(一致性规则),也被称为 Orphan Rule(孤儿规则)。这个规则是 Rust 编译器用于检查和保证类型安全的重要机制。

Implementing a Trait on a Type 中有提及。 这个规则避免了两个库对同一个类型实现相同的 trait,从而导致冲突和不可预测的行为

具体来说,Coherence Rule 有以下几个方面的限制和约束:

  1. 在同一个 crate 内部,所有实现同一个 Trait 的类型必须唯一。
  2. 如果一个 Trait 是在外部的 crate 中定义的,则不能在当前 crate 中为该 Trait 实现某个类型。否则,会导致两个库同时实现了同一个 Trait,编译器无法判断使用哪一个实现。
  3. 如果两个 crate 都为同一个类型实现了相同的 Trait,那么这两个 crate 必须显式地指定使用哪个实现。否则,编译器也无法判断使用哪一个实现。

总之,Coherence Rule 确保了 Trait 和其实现之间的关系是唯一的、确定的,从而让 Rust 编译器能够在编译时进行类型检查和类型推导,并在运行时保证代码的安全性和正确性。

下面是一个简单的示例:

假设我们有两个 crate:crate A 和 crate B。crate A 定义了一个名为 MyTrait 的 Trait,crate B 中定义了一个名为 MyType 的类型,并为其实现了 MyTrait。现在,我们希望在 crate A 中使用 MyType 类型和 MyTrait Trait。

首先,我们需要在 crate A 的 Cargo.toml 文件中添加以下依赖项:

toml
[dependencies]
crate_b = { version = "0.1", path = "../path/to/crate_b" }

然后,在 crate A 的代码中,我们可以这样调用 MyType 类型和 MyTrait Trait:

rust
use crate_b::MyType;

pub trait MyTrait {
    fn my_function(&self);
}

impl MyTrait for MyType {
    fn my_function(&self) {
        println!("Hello, world!");
    }
}

在这里,我们首先通过 use 语句引入了 crate B 中定义的 MyType 类型。然后,在 MyTrait Trait 的实现中,我们为 MyType 类型实现了该 Trait。

需要注意的是,如果我们在 crate A 中为 MyType 类型实现了 MyTrait Trait,而在 crate B 中也为其实现了同一个 Trait,那么就会触发 Coherence Rule 的限制,导致编译错误。为了解决这个问题,我们需要在代码中显式地指定使用哪一个实现,如下所示:

rust
use crate_b::MyType;

pub trait MyTrait {
    fn my_function(&self);
}

impl MyTrait for MyType {
    fn my_function(&self) {
        println!("Hello from crate B!");
    }
}

impl MyTrait for crate_a::MyType {
    fn my_function(&self) {
        println!("Hello from crate A!");
    }
}

在这里,我们为 MyType 类型分别实现了两个不同的 MyTrait Trait 实现,分别输出不同的字符串。然后,在使用时,我们需要显式地指定使用哪个实现:

rust
use crate_a::MyType;
use crate_a::MyTrait;

fn main() {
    let x = MyType;
    crate_b::MyTrait::my_function(&x);  // 输出:Hello from crate B!
    MyTrait::my_function(&x);           // 输出:Hello from crate A!
}

在这里,我们在调用 my_function 方法时,分别使用了 crate_b::MyTrait::my_functionMyTrait::my_function 两个不同的 Trait 实现。

如果您遇到需要绕过孤儿规则限制的情况,则可以使用 newtype 模式

Sized?Sized

Sized 是一个 Rust trait,用于表示编译时已知类型的大小。所有的 Rust 类型都必须实现此 trait,因为在编译时 Rust 需要知道每个类型占用的内存大小以便合理地分配内存。

你可以使用 Sized trait 来指定函数参数或者某些泛型的限制条件。例如,fn foo<T: Sized>(arg: T) 表示 arg 参数必须是一个大小已知的类型。

因为大多数类型都是大小已知的,所以在 Rust 中 Sized trait 很常见,并且通常不需要显式地指明

在 Rust 中,?Sized 是一种泛型类型限制语法,表示一个类型可以是 Sized 的也可以是动态大小的(即不确定大小的)。它通常用于那些需要支持动态大小类型的代码中,例如定义 trait 或者实现类型参数。

在 Rust 中,如果将一个类型的大小称为静态大小,那么动态大小类型就指那些只能在运行时才能确定其大小的类型。通常来说,这类类型都是通过动态内存分配而来的,例如 Vec<T>Box<T> 等等。

带有 ?Sized 后缀的泛型类型参数则表示该类型可以是静态大小的,也可以是动态大小的。例如:

rust
trait MyTrait<T: ?Sized> {
    fn my_method(&self, arg: &T);
}

struct MyStruct<T: ?Sized> {
    data: T,
}

impl<T: ?Sized> MyTrait<T> for MyStruct<T> {
    fn my_method(&self, arg: &T) {
        // ...
    }
}

fn main() {
    let x = MyStruct { data: "hello" };
    let y = MyStruct { data: vec![1, 2, 3] };

    x.my_method(&"world");
    y.my_method(&vec![4, 5, 6]);
}

在这个示例程序中,我们定义了一个泛型 trait MyTrait,它的泛型类型参数 T 带有 ?Sized 后缀表示可以是动态大小类型。 然后我们定义了一个结构体 MyStruct,它的泛型类型参数 T 同样带有 ?Sized 后缀,表示可以是静态大小类型或动态大小类型。 最后我们通过实现 MyTrait trait 来给 MyStruct 类型添加一个方法,这个方法接受一个实现了泛型类型 T 的引用参数,并对其进行处理。

main 函数中,我们分别创建了两个 MyStruct 对象,一个包含字符串数据,另一个包含整数数组数据。我们对这两个对象调用了 my_method 方法,传递了不同类型的参数。由于 MyTrait 的泛型类型参数 T 带有 ?Sized 后缀,因此可以接受动态大小类型的参数。

注意,在使用 ?Sized 语法时需谨慎,因为一些操作是只能在静态大小类型上执行的,比如直接访问数组元素、对类型进行栈分配等等。

如果你的代码需要明确地知道类型的大小,请避免使用 ?Sized 语法

Released under the MIT License