在 Rust 中,Sizedtrait 是一个非常基础且重要的 trait,它标志着类型的大小在编译时是已知的。
简单来说:绝大多数类型都是Sized的,而“不定长类型”(DST)则不是。
下面从几个方面来深入理解:
1. 隐式约束(默认行为)
Rust 中,泛型类型参数默认带有Sized约束。这意味着如果你写:
rust
fn example<T>(x: T) { // ... }编译器实际认为你写的是:
rust
fn example<T: Sized>(x: T) { // ... }这表示参数x必须是一个编译时就知道确切大小的类型(比如i32、f64、或者结构体实例),这样才能在栈上分配内存或安全地传参。
2. 什么时候类型不是Sized?(即?Sized)
有三类常见的类型不满足Sized(即大小不定):
切片(Slice):如
[u8](注意不是&[u8],引用的大小是固定的)。Trait 对象(dyn Trait):如
dyn Iterator,因为具体实现的大小未知。结构体的最后一个字段如果是 DST:那么这个结构体本身也会变成 DST。
如果你想让泛型参数能接受这些“不定长”类型,就需要使用?Sized来“解除”默认的Sized约束:
rust
fn example<T: ?Sized>(x: &T) { // 注意:通常通过引用或指针传递 // ... }3. 为什么需要Sized?
因为编译器需要知道如何为变量分配内存:
栈分配:局部变量默认在栈上,栈要求固定大小。
传参:函数参数默认按值传递,需要知道大小。
对于非Sized类型(DST),你无法直接将其作为变量或参数,而是必须通过指针(引用&、Box、Rc等)来间接访问,因为指针的大小是固定的(例如&[u8]在 64 位系统上是 16 字节,包含了指针和长度)。
4. 实际代码示例
错误写法(编译不通过):
rust
fn bad(x: [u8]) { } // 错误:`[u8]` 的大小在编译时未知正确写法(通过引用):
rust
fn good(x: &[u8]) { } // 正常,因为 &[u8] 是 Sized泛型中的使用:
rust
// 默认只能用于 Sized 类型 fn func1<T>(a: T) { } // 可以用于任意类型(包括 DST),但必须通过引用 fn func2<T: ?Sized>(a: &T) { } fn main() { func1(3i32); // OK // func1([1,2,3] as &[u8]); // 尝试传 DST 会报错 let slice: &[u8] = &[1, 2, 3]; func2(slice); // OK,T 被推导为 [u8](不定长),通过引用传递 }5. 特殊的一点:strvs&str
str是字符串切片,是 DST(不定长),不是Sized。&str是一个引用(胖指针),是Sized。
这就是为什么函数参数我们总是用&str而不是str。
总结
| 概念 | 含义 |
|---|---|
T: Sized | T的大小在编译时已知(默认情况)。 |
T: ?Sized | T可以是Sized的,也可以不是(用于放宽限制)。 |
| DST 例子 | [u8]、dyn Trait、str。 |
| 如何操作 DST | 必须通过指针(&、Box、Rc等)。 |
如果你在定义数据结构时想让字段存储不定长数据(比如 trait 对象),通常需要加上?Sized约束,并配合指针使用,最常见的就是Box<dyn Trait>。
如果还有关于Sized的具体应用场景(比如自定义智能指针)想深入探讨,可以随时告诉我。