Sized ------ 数据的大小
一、这个 Trait 定义了数据的什么行为
Sized 是 Rust 中最基础的 Trait------它甚至不需要你手动实现:
rust
// Sized trait 的定义(实际上在 std::marker 中)
pub trait Sized { } // 自动实现,不需要 impl
Sized 的含义:"这个类型在编译期有已知的固定大小。"
rust
use std::mem::size_of;
// 这些类型都是 Sized------编译器知道它们有多大
size_of::<i32>(); // 4 字节
size_of::<f64>(); // 8 字节
size_of::<[i32; 10]>(); // 40 字节(4 * 10)
size_of::<(i32, f64)>(); // 16 字节(4 + 8 + padding)
// 这些类型不是 Sized------编译器不知道它们有多大
// size_of::<[i32]>(); // ❌ 编译错误:未确定大小的切片
// size_of::<str>(); // ❌ 编译错误:未确定大小的字符串
// size_of::<dyn Debug>(); // ❌ 编译错误:未确定大小的 trait 对象
1.1 Sized 的隐式约束
Sized 是所有泛型参数的默认约束:
rust
// 这两个签名等价:
fn example<T>(x: &T) {}
fn example<T: Sized>(x: &T) {} // T: Sized 是隐式的
要接受动态大小类型(DST),必须显式写出 ?Sized:
rust
// 接受任意类型,包括 DST
fn example<T: ?Sized>(x: &T) {}
// ↑ "T 可能不是 Sized"
1.2 为什么几乎所有类型都是 Sized
Rust 编译器需要知道每个类型的大小,以便:
-
在栈上分配空间(每个函数帧的大小是编译期确定的)
-
计算结构体的字段偏移
-
确定数组的步长
-
生成函数调用约定(参数和返回值的大小)
rust
// 编译器需要知道 T 的大小来分配栈空间
fn process<T>(x: T) { // T: Sized 是默认的
// 在栈上为 x 分配 sizeof::<T>() 字节
}
二、C 中是什么
2.1 C 中一切都是 Sized
在 C 中,每个类型都有编译期已知的大小------sizeof(T):
c
#include <stddef.h>
sizeof(int); // 4(通常)
sizeof(double); // 8
sizeof(struct Point); // struct 的大小(含 padding)
sizeof(int[10]); // 40(4 * 10)
C 中没有"编译期不知道大小"的类型。每个类型都被视为有确定大小。
2.2 C 中唯一的例外:灵活数组成员
C99 引入了灵活数组成员(flexible array member),它允许结构体的最后一个字段是未指定大小的数组:
c
// 灵活数组成员------大小不完全确定
struct FlexArray {
int len;
char data[]; // 灵活数组成员:大小在运行时决定
};
// sizeof 不包含 data 的大小:
sizeof(struct FlexArray); // = sizeof(int)(可能 + padding)
// 实际分配时需要手动计算:
struct FlexArray* fa = malloc(sizeof(struct FlexArray) + 100);
fa->len = 100;
fa->data[0] = 'a'; // 手动管理------不知道是否越界
C 对灵活数组成员的处理:
-
sizeof不包括数组成员的大小 -
编译器不会检查
data[i]是否越界 -
程序员必须手动计算和分配额外空间
-
没有类型系统来表达"这是一个不确定大小的类型"
2.3 C 中数组退化为指针
c
// C 中数组参数退化为指针------丢失大小信息
void process(int arr[]) {
// arr 是 int*,不是 int[10]
// sizeof(arr) == sizeof(int*) // 8(64 位),不是 40!
}
C 中数组作为函数参数时"变成"了指针------这个"丢失大小"就是 C 没有 ?Sized 概念的后果。C 把数组当做 Sized,但传参时悄悄把它变成了指针。
2.4 sizeof 在 C 中的局限
c
// sizeof 不能用于灵活数组成员
struct FlexArray* fa = /* ... */;
// sizeof(fa->data) // ❌ 编译错误:对不完全类型使用 sizeof
// void 是不完全类型
// sizeof(void) // ❌ 编译错误:对不完全类型使用 sizeof
// 函数类型不能 sizeof
// sizeof(void()) // ❌ 编译错误
C 的 "不完全类型"(incomplete type)是唯一接近 ?Sized 的概念,但它不能出现在函数参数中、不能作为变量的类型、不能在泛型上下文中使用(C 也没有泛型)。
三、C 的问题
3.1 没有"动态大小类型"的概念
C 不能表达"这个类型的大小在编译期不确定":
c
// C 中无法安全地传递一个"不确定大小"的数组视图
// 只能用 int* + size_t
void process(int* data, size_t len);
// int* 丢失了"这是数组还是单个 int"的信息
// len 的准确性全靠调用者保证
3.2 灵活数组成员不安全
c
struct FlexArray {
int len;
char data[];
};
struct FlexArray* create(int n) {
struct FlexArray* fa = malloc(sizeof(struct FlexArray) + n);
fa->len = n;
return fa;
}
// 问题 1:无法在栈上分配
// struct FlexArray fa; // ❌ 编译器不知道大小
// 问题 2:不能作为数组元素
// struct FlexArray arr[10]; // ❌ 不能
// 问题 3:复制时不会复制 data
// struct FlexArray fa2 = *fa; // ❌ 只复制了 len,没有复制 data
// 问题 4:越界无保护
fa->data[1000] = 'x'; // ✅ 编译通过,运行时可能越界
3.3 数组退化为指针丢失大小
c
void process(int arr[10]) {
// 10 被忽略了------arr 是 int*
// 无法在这里检查访问是否越界
for (int i = 0; i < 10; i++) { // 只能靠程序员记住"约定"的 10
arr[i] = 0;
}
}
C 无法在原地保留数组的大小信息------传参就丢失。
3.4 C 没有统一的泛型约束来表达"大小不确定"
C 没有泛型,因此也就没有"对一个可能不确定大小的类型做操作"的需求。但也正因如此,C 缺少所有建立在 ?Sized 上的抽象能力:
-
没有切片类型(
&[T]) -
没有字符串切片(
&str) -
没有 Trait 对象(
dyn Trait)
四、Rust 为什么需要这个 Trait
4.1 Sized 是类型系统的基础设施
Sized 自动实现,不需要程序员干预。它的核心作用是作为泛型的默认约束:
rust
// 默认:T: Sized
struct Container<T> { // T 默认是 Sized
data: T, // 编译器需要知道 T 的大小来布局结构体
}
// 显式:T: ?Sized ------ 允许 DST
struct Container<T: ?Sized> {
data: Box<T>, // T 可能不是 Sized,所以用 Box
}
4.2 Sized 与栈分配
rust
// 栈分配要求类型是 Sized
let x: i32 = 42; // ✅ i32 是 Sized------在栈上分配 4 字节
let y: [i32; 5] = [0; 5]; // ✅ [i32; 5] 是 Sized------20 字节
// 但:
// let z: [i32] = [1, 2, 3]; // ❌ 编译错误![i32] 不是 Sized
// [i32] 没有编译期已知的大小------不能直接在栈上分配
// 必须用切片引用:&[i32](胖指针,16 字节,是 Sized 的)
4.3 动态大小类型(DST)必须放在指针后面
rust
use std::mem::size_of;
// DST 类型本身不是 Sized:
// [i32] ------ 切片(长度不定)
// str ------ 字符串(字节数不定)
// dyn Trait ------ Trait 对象(具体类型不定)
// 但 DST 的胖指针引用是 Sized 的:
size_of::<&[i32]>(); // 16(ptr + len)
size_of::<Box<[i32]>>(); // 16(ptr + len)
size_of::<&str>(); // 16(ptr + len)
size_of::<Box<dyn Debug>>(); // 16(ptr + vtable)
size_of::<Rc<dyn Trait>>(); // 16(ptr + vtable)
这就是 Sized 的核心意义:它让编译器知道什么时候可以在栈上分配,什么时候必须通过指针间接使用。
rust
Sized 类型 → 可以直接使用(栈分配、作为结构体字段内联)
?Sized 类型 → 必须通过指针使用(&、Box、Rc 等)
4.4 Sized 与 ?Sized 的边界
?Sized 约束主要在泛型函数中使用,以便同时接受 Sized 和 DST:
rust
// 只接受 Sized 类型
fn only_sized<T>(val: T) { // T: Sized 是默认的
// T 在栈上
}
// 接受所有类型(包括 DST)
fn accept_dst<T: ?Sized>(val: &T) {
// T 可能不是 Sized,所以通过引用传递
}
// 标准库中的典型例子:
// impl<T: ?Sized> Clone for Box<T> { ... }
// Box 可以对任何类型实现 Clone------包括 [i32] 和 dyn Trait
// 因为 Box 本身是 Sized 的(胖指针),不管 T 是不是 Sized
4.5 标准库中的 ?Sized 使用
rust
// Box<T: ?Sized> ------ Box 可以装 DST
let box_slice: Box<[i32]> = Box::new([1, 2, 3]); // [i32] 是 DST
let box_str: Box<str> = Box::from("hello"); // str 是 DST
let box_dyn: Box<dyn Debug> = Box::new(42); // dyn Debug 是 DST
// 切片引用就是胖指针------知道长度
let slice: &[i32] = &[1, 2, 3, 4];
// 在 64 位系统上:16 字节(ptr + len)
// Trait 对象也是胖指针------知道 vtable
let debug: &dyn Debug = &42;
// 在 64 位系统上:16 字节(ptr + vtable)
4.6 Sized 与 C 的 sizeof 对比
| 维度 | C sizeof |
Rust Sized |
|---|---|---|
| 是一个操作符 | ✅ sizeof(T) |
❌ std::mem::size_of::<T>() 是函数 |
| 对 DST 的行为 | ❌ 灵活数组成员不包含在 sizeof 中 | ❌ DST 不能调用 size_of |
| 作为类型约束 | ❌ 不能表达"T 必须有大小" | ✅ T: Sized 默认约束 |
| 作为泛型边界 | ❌ 没有泛型 | ✅ T: ?Sized 显式放宽 |
| 运行时检查 | ❌ 无法检测 | ✅ 通过 DST 胖指针携带长度 |
4.7 一个实际的例子:为什么需要 ?Sized
rust
// 想写一个可以接受"任意类型的引用"的函数
// ❌ 错误:T 默认是 Sized
fn print_it<T: Debug>(val: &T) {
println!("{:?}", val);
}
print_it(&[1, 2, 3]); // ✅ &[i32; 3] 是 &T,T 是 [i32; 3](Sized)
// print_it(&[1, 2, 3] as &[i32]); // ❌ &[i32] 中 [i32] 不是 Sized
// ✅ 正确:T: ?Sized
fn print_it2<T: ?Sized + Debug>(val: &T) {
println!("{:?}", val);
}
print_it2(&[1, 2, 3] as &[i32]); // ✅ [i32] 是 ?Sized
print_it2("hello world"); // ✅ str 是 ?Sized
五、与 C 程序员的对话
"C 中所有类型都有 sizeof,Rust 不也一样?"
C 程序员 :"C 中所有类型都可以
sizeof,Rust 的Sized不是一样的东西吗?"
Rust :"运行时效果一样------两者都知道类型的大小。但区别在于类型系统:C 中所有 类型都是 Sized(除了灵活数组成员的一些特殊处理)。Rust 明确区分了 Sized 和 ?Sized------有些类型([i32]、str、dyn Trait)不是 Sized。这种区分延伸到泛型约束中------你可以写T: ?Sized来表达'这个泛型参数可能不是一个有固定大小的类型'。C 没有这种能力。"
c
// C ------ 所有类型都是"有固定大小的"
int x; // ✅ 知道大小
int arr[10]; // ✅ 知道大小
int[] /* 参数 */; // ❌ 退化为指针------但这是传参的特殊规则,不是类型系统的特性
rust
// Rust ------ 显式区分
let x: i32; // ✅ Sized
let arr: [i32; 10]; // ✅ Sized
let slice: &[i32]; // ✅ &[i32] 是 Sized(胖指针)
// [i32] // ❌ 不是 Sized------不能直接使用
"那 ?Sized 有什么用?"
C 程序员 :"我写代码从来没遇到过
需要不确定大小的类型,?Sized 在什么场景用?"
Rust :"写泛型代码时用到------尤其是标准库作者。比如Box<T: ?Sized>让 Box 可以装[i32]、str、dyn Trait。如果你只写应用代码,确实很少直接接触 ?Sized。但如果你用过&[i32]、Box<dyn Trait>、Rc<str>------这些就依赖于?Sized的存在。"
rust
// 标准库中 ?Sized 的使用
impl<T: ?Sized> Clone for Box<T> { /* ... */ }
impl<T: ?Sized + PartialEq> PartialEq for Rc<T> { /* ... */ }
impl<T: ?Sized> Deref for Box<T> { /* ... */ }
// 如果你自己写容器,可能需要:
pub struct MyBox<T: ?Sized> {
ptr: *mut T,
}
六、小结
6.1 Sized 在 Rust 类型系统中的地位
rust
Sized(默认约束)
│
├── 所有基本类型:i32, f64, bool, char ...
├── 所有 struct/enum(除非内部有 ?Sized 字段)
├── 所有引用 &T、&mut T(普通引用是 Sized 的)
└── 所有胖指针 &[T]、Box<dyn Trait> ...
│
└── 它们指向的 [T]、str、dyn Trait 是 ?Sized 的
Sized 是 Rust 类型系统中"看不见但处处都在"的基础设施。 它是所有泛型参数的默认约束,是栈分配的前提条件,是堆分配与指针间接使用的分界线。
6.2 C 没有的概念
| Rust 的概念 | C 中有没有 | 为什么 |
|---|---|---|
Sized --- 标记类型有固定大小 |
有(所有类型隐含) | 但 C 无法在类型系统中表达 |
?Sized --- 标记类型不一定有固定大小 |
没有 | C 没有 DST 的概念 |
[T] --- 动态大小切片 |
没有 | 数组退化为指针 |
str --- 动态大小字符串 |
没有 | C 用 char* + NULL 结尾 |
dyn Trait --- 动态大小 trait 对象 |
没有 | C 没有 trait |
6.3 一句话总结
C 中所有类型都是"固定大小"的,语言没有"不确定大小的类型"这个概念------灵活数组成员是唯一的例外,但它的处理方式不安全(无边界检查、无类型系统表达)。Rust 的
SizedTrait 明确区分了"固定大小"和"不确定大小":Sized 类型可以直接使用(栈分配、字段内联),?Sized类型必须通过指针间接使用。这个区分让 Rust 可以安全地表达切片、字符串切片、Trait 对象等动态大小类型,同时保持编译器的类型检查。