Sized 是 Rust 中一个极其特殊的标记 Trait(Marker Trait),它既没有方法,也极少需要手动实现,但却深刻地影响着 Rust 的类型系统、内存布局和 trait 对象的使用方式。
1、什么是 Sized?
Sized 的定义极其简单,位于 std::marker 模块中:
rust
pub trait Sized { }
一个类型在编译期具有固定且已知的大小 。Rust 中绝大多数类型都是 Sized 的,包括所有基本类型、结构体、枚举、数组、元组等。
如果一个类型的大小在编译期无法确定,它就被称为动态大小类型(DST, Dynamically Sized Type)。典型的 DST 包括:
- 切片类型
[T] - 字符串切片
str(不是&str) - trait 对象
dyn Trait - 包含一个 DST 作为最后一个字段的结构体
rust
// 这些都是 DST,无法直接使用
let s: str = *"hello"; // 编译错误:size 未知
let arr: [i32] = [1, 2, 3]; // 编译错误:size 未知
let dyn_obj: dyn Display = ...; // 编译错误:size 未知
由于 DST 的大小未知,它们无法被放在栈上,也不能作为函数参数按值传递。只能在指针后面 使用它们,比如 &str、&[T]、Box<dyn Trait> 等。
2、隐式 Sized 绑定
Rust 的类型参数默认是 Sized 的 。这意味着当你写一个泛型函数时,编译器实际上帮你在内部添加了 Sized 约束:
rust
// 你写的代码
fn foo<T>(x: T) { ... }
// 编译器实际看到的
fn foo<T: Sized>(x: T) { ... }
这种隐式绑定覆盖了以下场景:
- 泛型类型参数
T - 泛型结构体/枚举中的字段
impl Trait的返回类型
3、 使用 ?Sized 解除限制
当你需要在泛型中接受 DST 时,需要使用特殊语法 ?Sized,意为 T 可以是 Sized 的,也可以不是。
rust
// 解除 Sized 限制,T 可以是 DST
fn bar<T: ?Sized>(x: &T) {
// x 是指针,所以即使 T 是 DST 也没问题
}
?Sized 只能在以下位置使用:
- 泛型类型参数:
T: ?Sized - 结构体泛型参数的最后一个字段:
struct S<T: ?Sized> { tail: T }
不能在关联类型、trait 自身定义或函数返回值中使用。
rust
use std::fmt::Debug;
fn print_test<T: Debug + ?Sized>(t: &T) {
println!("{:?}", t);
}
fn main() {
print_test("ssss");
print_test(&[2, 5, 6]);
print_test(&35);
}
4、标准库中的应用
| 类型/模块 | 应用方式 | 说明 |
|---|---|---|
Box<T> |
impl<T: ?Sized> Box<T> |
Box 可以指向堆上的 DST,如 Box<[i32]> 或 Box<dyn Error>。 |
Rc<T> / Arc<T> |
impl<T: ?Sized> Rc<T> |
引用计数指针也可以指向 DST。 |
std::fmt::Debug |
trait Debug: ?Sized |
允许对 &str、[i32] 等切片直接调用 .fmt()。 |
AsRef<T> |
impl<T: ?Sized, U: ?Sized> AsRef<U> for T |
许多转换逻辑需要处理不定长类型。 |
5、Sized 和 ?Sized 的使用
- 默认不动 :在绝大多数泛型编程中,保持默认的
Sized约束。这保证了类型可以按值传递、存储在栈上,且性能最优。 - 需要灵活性时用
?Sized:- 当你的函数接收的是引用 (
&T)或智能指针 (Box<T>),且你希望它能通吃"具体类型"和"切片/Trait对象"时。 - 典型签名:
fn foo<T: ?Sized>(x: &T)。
- 当你的函数接收的是引用 (
- 设计 Trait 时注意 :
- 如果希望 Trait 能被用作
dyn Trait,不要给 Trait 本身加Sized约束。 - 如果 Trait 中的某些方法依赖于静态大小(如返回
Self),给该方法加where Self: Sized。
- 如果希望 Trait 能被用作
理解 Sized 和 ?Sized 的核心在于明白:**Rust 默认追求静态确定的内存布局,而 ?Sized 是为了在必要时(通过指针间接访问)打破这一限制,以支持多态和切片操作。