例子:
例子如下:
bash
cargo new demo
cd demo
修改 main.rs 如下:
rust
use std::marker::PhantomData;
use std::any::type_name;
// 一个空标记结构体,用来打印类型
struct TypePrinter<T> {
// 只用来"携带"类型 T,不占内存
_phantom: PhantomData<T>,
}
impl<T> TypePrinter<T> {
// 创建一个打印专用实例
fn new() -> Self {
Self {
_phantom: PhantomData,
}
}
// 打印当前携带的类型名字
fn print_type(&self) {
println!("当前类型:{}", type_name::<T>());
}
}
fn main() {
// 打印 i32 类型
let p1 = TypePrinter::<i32>::new();
p1.print_type();
// 打印 String 类型
let p2 = TypePrinter::<String>::new();
p2.print_type();
// 打印 &str 类型
let p3 = TypePrinter::<&str>::new();
p3.print_type();
// 打印 Vec<bool> 类型
let p4 = TypePrinter::<Vec<bool>>::new();
p4.print_type();
}
运行下面的命令:
bash
cargo run
预期输出:
当前类型:i32
当前类型:alloc::string::String
当前类型:&str
当前类型:alloc::vec::Vec<bool>
解析 PhantomData
PhantomData 是 Rust 标准库 std::marker 中的一个零大小类型(ZST),它不占用运行时内存,但能在编译期向编译器传递关键的类型语义信息,用于表达逻辑关联、约束生命周期或控制类型行为。
PhantomData 本身没内存、没字段、不占空间,只是骗 / 告诉编译器:
「我这个结构体,逻辑上关联了某个泛型 / 生命周期」
不然编译器会报错:泛型参数没用到、生命周期没用上。
口诀:
泛型用不到,编译器发飙;
加个 PhantomData,安静又达标。
问题:凭什么会需要「只有类型、没有数据」?正常写代码不都是字段存数据吗?
回答:例子场景如下:
两种「长得一样,但绝对不能混用」的 ID
UserId
OrderId
底层都是 u64,数值完全一样。
如果直接写:
rust
struct Id(u64);
用户ID 能直接传给「删除订单」接口,出线上事故。
解决方案:只挂类型标签,不加数据
rust
use std::marker::PhantomData;
struct User;
struct Order;
// 只有一个数值 + 一个空类型标记,没有任何额外数据
struct Id<T>(u64, PhantomData<T>);
fn fetch_user(id: Id<User>) {}
fn delete_order(id: Id<Order>) {}
运行时只存一个 u64,不能多存东西;
但编译期必须是两种完全不同的类型,防止传参搞错。
这就是:只带类型、不带内容 的刚需。
这也是 RUST 确保内存安全的方式之一。