从C到Rust:最基础的Trait Sized

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]strdyn 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]strdyn 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 的 Sized Trait 明确区分了"固定大小"和"不确定大小":Sized 类型可以直接使用(栈分配、字段内联),?Sized 类型必须通过指针间接使用。这个区分让 Rust 可以安全地表达切片、字符串切片、Trait 对象等动态大小类型,同时保持编译器的类型检查。

相关推荐
techdashen2 小时前
把正确性藏进类型里:从 Go 的 io.Reader 到 Rust 的 API 设计
网络·golang·rust
前端之虎陈随易2 小时前
Rust、Golang、MoonBit 编译成 WASM,体积和速度差距有多大?
golang·rust·wasm
doiito(Do It Together)20 小时前
我用 Rust 写了个 AI 媒体管家:Gliding Horse 赋能 media_agent,目标是让 ComfyUI 工作流彻底自动化
人工智能·架构·rust·媒体
独孤留白1 天前
从C到Rust:告别 C 的"指针 + 长度"手动模式
前端·rust
咸甜适中1 天前
rust语言学习笔记(指针十一)Cow<T>(写时克隆)
笔记·学习·rust
doiito1 天前
【Agent Harness】 给 ComfyUI 装上一个 Rust 大脑:media_agent 架构深度揭秘
ai·rust·架构设计·系统设计·ai agent
花褪残红青杏小2 天前
Rust图像处理第11节-故障风 RGB 通道偏移:错位错色制造电子故障
rust·webassembly·图形学
花褪残红青杏小2 天前
Rust图像处理第10节-浮雕/雕刻滤镜:邻域差值生成凹凸效果
rust·webassembly·图形学