惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Recent Announcements
Recent Announcements
J
Java Code Geeks
U
Unit 42
GbyAI
GbyAI
大猫的无限游戏
大猫的无限游戏
L
LangChain Blog
D
Docker
F
Fortinet All Blogs
N
Netflix TechBlog - Medium
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
罗磊的独立博客
I
InfoQ
The Cloudflare Blog
小众软件
小众软件
V
Visual Studio Blog
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
Engineering at Meta
Engineering at Meta
S
SegmentFault 最新的问题
爱范儿
爱范儿
Hugging Face - Blog
Hugging Face - Blog
P
Proofpoint News Feed
V
V2EX
月光博客
月光博客
Martin Fowler
Martin Fowler

博客园 - PKICA

c++ unordered_map‌底层实现 单片机 MCU,嵌入式,MPU,DSP,FPGA,PLC 博文阅读密码验证 - 博客园 博文阅读密码验证 - 博客园 博文阅读密码验证 - 博客园 博文阅读密码验证 - 博客园 博文阅读密码验证 - 博客园 博文阅读密码验证 - 博客园 博文阅读密码验证 - 博客园 博文阅读密码验证 - 博客园 博文阅读密码验证 - 博客园 博文阅读密码验证 - 博客园 博文阅读密码验证 - 博客园 博文阅读密码验证 - 博客园 rust线程-std::thread::park和unpark 配合Builder实现轻量级的线程挂起与唤醒 rust自定义线程属性std::thread::Builder rust线程 rust关联函数 rust高并发设计实践进阶 rust高并发设计实践 rust性能优化与安全边界 联合体union在rust和C语言中有什么区别 rust并发与异步编程 如何预防rust不良的代码设计总结 rust类型系统与零成本抽象 rust内存模型 告别大显存依赖!用 Rust 新一代深度学习框架 Burn 打造纯 CPU 文本分类推理引擎 编译配置解答 git实用命令 rust底层设计理念值得注意的几个地方总结
rust类型系统标记
PKICA · 2026-07-21 · via 博客园 - PKICA

在 Rust 中,“类型系统标记”(Type System Markers)或 标记 trait(Marker Traits) 是一种非常独特的语言特性。

它们是一类内部没有任何方法(Method)或关联类型(Associated Types)的空 Trait。它们的存在不是为了提供具体的代码功能,而是像一个个“标签”贴在类型上,明确告知编译器该类型具备某些特殊的内存或并发属性。

以下为系统拆解 Rust 类型系统中的 4 类核心标记,以及它们如何协助编译器保障代码安全:


一、 核心四大内置标记 Trait (OIBIT)

这四个标记最广为人知,它们属于“自动 Trait”(Auto Traits),由编译器根据结构体内字段的属性自动“继承”,不需要你手动去实现。

1. Send —— 跨线程所有权转移标记

  • 含义:如果一个类型实现了 Send,意味着它的所有权可以安全地从一个线程传递到另一个线程。
  • 典型代表:几乎所有独占或基本类型(如 i32, String, Box<T>)都是 Send
  • 反例:裸指针 *mut T 不是 Send,因为跨线程操作裸指针极易引发数据竞争。

2. Sync —— 跨线程并发引用标记

  • 含义:如果一个类型实现了 Sync,意味着多个线程可以同时持有它的不安全/安全引用(&T)。
  • 规则判定:如果 &TSend,那么 T 就是 Sync
  • 典型代表:原子操作类型 AtomicBool、带互斥锁的包装类 Mutex<T>
  • 反例:Rc<T>(引用计数器)。由于内部的非原子计数加减未加锁,多线程共享引用会导致计数错乱,因此它既不是 Send 也不是 Sync

3. Copy —— 内存按位复制标记

  • 含义:改变赋值语义。通常 Rust 赋值是“移动(Move)”所有权,但贴上 Copy 标签后,赋值或传参会触发隐式的内存按位复制(Bitwise Copy,等同于 C 语言的 memcpy)。
  • 前提条件:该类型必须先实现 Clone,且其内部绝不能包含任何管理堆内存或资源的智能指针(如 StringBox,否则会导致双重释放 Double Free)。

4. Sized —— 编译期大小确定标记

  • 含义:告知编译器该类型在编译期间其占用的内存字节数是明确固定的。
  • 默认行为:泛型函数如 fn func<T>(arg: T) 默认隐含了 T: Sized 约束。如果是动态大小类型(DST,如 str[u32]),则必须写作 T: ?Sized(表示大小可能不确定)。

二、 逻辑标记:生命周期与虚无标记 (PhantomData)

有时候,你定义了一个泛型结构体,但由于特定的底层设计(如处理 FFI 或裸指针),结构体的内部字段中并没有真正用到这个泛型参数。此时编译器会直接报错:parameter is never used

为了给编译器“贴个假标签”满足类型检查,就需要用到 std::marker::PhantomData(幽灵数据)。

use std::marker::PhantomData;

// 我们在做 C 语言 FFI 时,PowerState 实际上是一个裸指针,但在 Rust 里它想和某个生命周期 'a 绑定
struct PowerState<'a> {
    raw_ptr: *mut std::os::raw::c_void,
    _marker: PhantomData<&'a ()>, // 显式标记:虽然没存引用,但逻辑上告知编译器该结构体受到生命周期 'a 的约束
}
  • PhantomData 占用 0 字节 内存,在编译后会彻底消失。
  • 它纯粹是提供给 Rust 借用检查器(Borrow Checker)的视觉标记,防止你在生命周期结束后继续使用该裸指针。

三、 零大小标记类型(ZST)与状态机

在更高阶的 Rust 架构设计中,标记经常被用作类型状态模式(Type-State Pattern)。通过定义一系列 0 字节的空结构体作为标记,可以在编译期强制约束对象的行为状态。

// 定义一组状态标记
struct Initializing;
struct Connected;
struct Disconnected;

// 状态机结构体
struct NetworkConnection<State> {
    address: String,
    _state: std::marker::PhantomData<State>,
}

impl NetworkConnection<Initializing> {
    // 只有在 Initializing 状态标记下,才能调用 connect 方法
    pub fn connect(self) -> NetworkConnection<Connected> {
        NetworkConnection { address: self.address, _state: PhantomData }
    }
}

这种标记系统让逻辑错误(如“在未连接时发送数据”)直接在编译阶段暴露,而不是在运行时崩溃。


四、 如何手动操控标记?(Negative Impls)

由于像 SendSync 是由编译器自动推导的,如果你用 transmute 包装了一些底层指针,或者想强制让一个原本安全的结构体变成不支持跨线程,可以利用独占写法将其移除:

#![feature(negative_impls)] // 当前需要不稳定特性支持

struct MySensitiveData {
    secret: i32,
}

// 显式告诉编译器:强行剥离该类型的 Send 标记!
impl !Send for MySensitiveData {}

这样一来,一旦有其他开发者尝试把 MySensitiveData 扔进多线程的 thread::spawn 中,Rust 编译器就会直接拒绝编译。

总结

Rust 的类型标记系统是其实现无畏并发(Fearless Concurrency)和内存零成本抽象的基石。它不增加运行负担,而是将原本需要靠程序员口头承诺或文档规范的“线程安全性”、“内存可复制性”,统统转产为编译期死守的类型标签。

结合最开始问到的 transmute 裸指针操作,在使用裸指针时,正是因为裸指针不具备任何默认的标记(非 Send/非 Sync),才迫使我们必须使用 unsafe 并在外层手动为其包装安全的标记组件。

参考资料

rust并发安全特性