news 2026/9/11 0:12:43

Comprehensive Rust 深入 unsafe(一):定义 unsafe 函数——以 ptr_to_ref 为例剖析责任转移与安全前置条件

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Comprehensive Rust 深入 unsafe(一):定义 unsafe 函数——以 ptr_to_ref 为例剖析责任转移与安全前置条件

Comprehensive Rust 深入 unsafe(一):定义 unsafe 函数——以 ptr_to_ref 为例剖析责任转移与安全前置条件

【免费下载链接】comprehensive-rustThis is the Rust course used by the Android team at Google. It provides you the material to quickly teach Rust.项目地址: https://gitcode.com/GitHub_Trending/co/comprehensive-rust

本篇技术指南聚焦 Google Android 团队维护的 Rust 教学仓库 comprehensive-rust 中《Unsafe Deep Dive》热身环节的核心一课——如何定义 unsafe 函数(对应课程文档 warm-up/unsafe-fn.md)。课程通过一个将裸指针转换为引用的ptr_to_ref示例,揭示了一个反直觉的事实:看起来完全安全的代码,实际上必须借助unsafe块才能成立。读完本文,你将掌握 unsafe 函数与普通函数的分界、unsafe关键字的两重角色、API 设计者如何通过"责任转移"把内存安全前置条件交给调用方,以及如何在标准库(如ptr::as_mut)中定位并解读这些前置条件。

热身课:四种 unsafe 语法形态

在进入ptr_to_ref之前,先建立全局坐标系。本仓库将 unsafe 语法拆成四个独立热身示例(见 warm-up.md):

语法形态语法示例对应课程文档
unsafe 块unsafe { ... }warm-up/unsafe-block.md
unsafe 函数unsafe fnwarm-up/unsafe-fn.md
unsafe trait 的实现unsafe impl { ... }warm-up/unsafe-impl.md
unsafe trait 的定义unsafe traitwarm-up/unsafe-trait.md

其中,"定义 unsafe 函数"这一课是全套 unsafe 教学的起点:它回答了一个根本性问题——什么样的 API 必须用unsafe fn声明,声明之后责任又转移给了谁

核心示例:ptr_to_ref,一段"看似安全"的代码

课程给出的示例代码如下(来源:unsafe-fn.md):

/// Convert a nullable pointer to a reference. /// /// Returns `None` when `p` is null, otherwise wraps `val` in `Some`. fn ptr_to_ref<'a, T>(ptr: *mut T) -> Option<&'a mut T> { if ptr.is_null() { None } else { // SAFETY: `ptr` is non-null unsafe { Some(&mut *ptr) } } }

初看之下,这段代码似乎完全可以脱离unsafe编写:if ptr.is_null()已经对空指针做了分支处理,非空分支似乎"顺理成章"地返回引用。然而课程明确指出:

"This looks as though it's safe code, however it actually requires an unsafe block."(这段代码看起来像是安全代码,但实际上需要一个 unsafe 块。)

关键点在于unsafe { Some(&mut *ptr) }中的解引用操作*ptr——将裸指针解引用并转换为可变引用,是编译器无法替你验证安全的操作。课程建议教师在讲解时高亮这一行解引用,让学员直观看到"问题出在哪里"。

为什么"非空指针"依然不够:指针并不都能变成引用

课程强调了一个反直觉的结论:

"Callers must ensure that theptris null, or that it may be converted to a reference."(调用方必须确保指针为 null,或者它可以被转换为引用。) "It may be counter-intuitive, but many pointers cannot be converted to references."(可能违反直觉,但很多指针并不能被转换为引用。)

为什么?课程给出的解释是:

"Among other issues, a pointer could be created that points to some arbitrary bits rather than a valid value."(指针可能指向任意比特,而不是一个合法的值。)

也就是说,*mut T只是一串内存地址,它既可以指向一个合法构造的T实例,也可能指向:

  • 未初始化的内存(garbage bits);
  • 已经释放/失效的对象;
  • 不满足对齐要求或生命周期约束的地址。

而 Rust 的引用有严格的合法性约束(对齐、有效生命周期、别名规则等)。从裸指针到引用的转换,隐含了"该指针必须 dereferenceable(可解引用)且满足 Rust 别名规则"等前置条件——这些条件无法通过ptr.is_null()这类运行时检查完全保障ptr_to_ref的函数体只验证了"非空"这一条,其余条件必须由调用方保证,这正是函数必须声明为 unsafe 的根本原因。

更深入的指针转引用规则,本仓库在 safety-preconditions/determining.md 中有完整讲解(详见下文"标准库中的对应物"一节)。

API 设计者的两条路:自行防御,还是转移责任

课程指出,面对"调用方可能传入非法指针"这一现实,API 设计者有两条路可走:

  1. 第一条路:自行承担防御职责——在函数内部穷尽所有非法输入的检查;
  2. 第二条路:用unsafe关键字把责任转移给调用方

为什么第一条路在泛型场景下"走不通"

课程对第一条路给出了明确的否定理由:

"The first path is a difficult one. We're accepting a generic type T, which is all possible types that implement Sized. That's a lot of types!"(第一条路很困难。我们接受泛型 T,它涵盖所有实现了 Sized 的类型,那可是一大堆类型!)

这里的论证非常精妙:fn ptr_to_ref<'a, T>(ptr: *mut T)中的T: Sized(默认约束)意味着该函数要为所有尺寸已知的类型负责。对每一种T,"什么样的指针才是合法指针"的判断标准都不同——例如T = u8T = 某个含引用字段的结构体,其合法性前置条件天差地别。想在泛型函数内部穷举并防御所有可能的非法输入,本质上不可行。

第二条路:unsafe 即责任契约

因此课程得出结论:

"Therefore, the second path makes more sense."(因此第二条路更有意义。)

即:把ptr_to_ref声明为unsafe fn,并在文档中写明调用方必须满足的前置条件(指针为 null,或可安全转换为引用)。unsafe关键字在这里是一份显式的责任契约:函数作者声明"我无法替你验证一切,请你满足这些前置条件",调用方则承诺"我已阅读并满足这些条件"。

这一结论与本仓库 introduction/responsibility-shift.md 中"unsafe 关键字转移责任"的表格完全呼应:

场景内存是否安全?内存安全责任方
Safe Rust编译器
Unsafe Rust程序员

课程原话:"Theunsafekeyword shifts responsibility for maintaining memory safety from the compiler to programmers. It signals that there are preconditions that must be satisfied."(unsafe关键字将维护内存安全的职责从编译器转移到程序员身上,它标志着存在必须满足的前置条件。)后文中,本课程统一使用术语safety preconditions(安全前置条件)来描述这种需要调用方保证的局面。

为什么"定义"unsafe 函数与"使用"unsafe 块是两回事

ptr_to_ref示例恰好同时演示了unsafe关键字的两种角色。本仓库在 introduction/two-roles.md 中将其总结为:

  1. 创建(Creating)带安全考量点的 APIunsafe fn get_unchecked(&self) { ... }unsafe trait Send {}。在这个角色下,你在告诉 API 的使用者:"请小心,你需要注意我的安全要求。"同时,API 作者有义务把需要何种小心(即安全要求)写进文档——"Unsafe APIs are not complete without documentation about safety requirements"(没有安全需求文档的 unsafe API 是不完整的)。
  2. 使用(Using)带安全考量点的 APIunsafe { *ptr }调用内置 unsafe 运算符、unsafe { x.get_unchecked() }调用 unsafe 函数、unsafe impl Send for Counter {}实现 unsafe trait。当unsafe紧邻花括号出现时,它表示作者已经小心过——"the author has been careful. They have verified that the code is safe"(作者已经仔细验证过代码是安全的)。

对应到ptr_to_ref

  • 定义层面:该函数虽然没有声明为unsafe fn(课程刻意如此设计以制造讨论点),但其函数体内部使用 unsafe 块调用了"解引用裸指针"这一编译器已知的 unsafe 操作;
  • 使用层面unsafe { Some(&mut *ptr) }块内的代码,是作者对"ptr已验证非空、调用方已保证指针合法"这一结论的担保声明。

值得注意的是,课程在此处留了一个值得深挖的张力点:ptr_to_ref以普通fn对外暴露,却在其内部执行 unsafe 操作。这引出一个更规范的写法问题——如果调用方仍可能传入非法指针,那么该函数应当整体声明为unsafe fn,把前置条件显式暴露给调用方,这正是课程"责任转移"哲学的直接推论。(关于 unsafe 函数的规范声明方式与调用约束,可继续学习本仓库的 unsafe-rust/unsafe-functions.md 与 unsafe-rust/unsafe.md。)

补充讲解:ptr_to_ref 在标准库中的真实对应物——ptr::as_mut

课程在"时间允许的附加内容"中给出了一条极具价值的线索:

"For example, theptr_to_reffunction on this slide actually exists in the standard library as theas_mutmethod on pointers."(本页的ptr_to_ref函数其实在标准库中就存在,即指针上的as_mut方法。)

也就是说,你不需要手写ptr_to_ref——标准库为裸指针提供了as_mut(以及对应的as_ref)方法,其语义与示例完全一致:指针为 null 时返回None,否则返回Some(&mut *ptr)这为学员提供了一个"照着标准库学 unsafe"的范本:标准库中大量方法(尤其是std::ptr上的方法)都以极规范的 Safety 文档公开了各自的前置条件。

本仓库在 safety-preconditions/determining.md 中进一步演示了如何查找as_mut的前置条件。课程给出了一份"去哪里查前置条件"的清单:

  • 函数 API 文档,尤其是其中的Safety 段落
  • 源码及其内部的 SAFETY 注释;
  • 模块级文档;
  • Rust Reference(参考手册)。

其中as_mut的官方 Safety 说明原文为:

SafetyWhen calling this method, you have to ensure that either the pointer is null or the pointer is convertible to a reference. (调用此方法时,你必须确保指针为 null,或者指针可被转换为引用。)

课程还提示,点击文档中 "convertible to a reference" 的超链接,可以追踪"指针到引用转换"的详细规则(即指针必须 dereferenceable)。以 Rust 1.90.0 版本为例,文档甚至指出"You must enforce Rust's aliasing rules. The exact aliasing rules are not decided yet"(你必须遵守 Rust 的别名规则,而确切的别名规则尚未最终定案)——这说明指针转引用的部分规则仍在演进,更凸显了以文档为准、以unsafe显式声明责任的重要性

安全工作流:理解前置条件、验证并留下 SAFETY 注释

理解了责任转移与前置条件之后,本仓库 introduction/impact-on-workflow.md 给出了落地到日常开发的建议:

写代码时

  • 验证你理解每一个unsafe函数/trait 的前置条件;
  • 检查这些前置条件是否确实被满足;
  • 把你的推理过程写成safety comments(安全注释)

这正是示例代码中// SAFETY: \ptr` is non-null注释的由来——它不是可有可无的装饰,而是"作者已经验证过"的书面证据。同样的做法也出现在本仓库的配套热身课 [warm-up/unsafe-block.md](https://link.gitcode.com/i/656ebeb407863fec1f460d35366572ad) 中:那里使用vec![0, 1, 2, 3, 4]配合get_unchecked(i)i = len/2),演示了先触发编译器报错(unsafe 调用必须包在 unsafe 块内),再补上unsafe { ... }// SAFETY: `i` must be within 0..numbers.len()` 注释的完整流程。

强化代码评审

  • 评审链路建议为:自评 → 同行评审 →(必要时)unsafe Rust 专家评审;
  • 遇到拿不准的情况,升级给"对你的代码与推理感到放心"的人。

课程对此的解释很务实:"There are only a few unsafe Rust experts, and they are very busy, so we need to optimally use their time."(unsafe Rust 专家很少且非常忙碌,我们需要最优地利用他们的时间。)简单 unsafe 代码由作者与主评审自行把关,疑难场景才请专家介入。

小结:从 ptr_to_ref 学到的 unsafe 函数设计要点

要点结论出处
判据若函数内部依赖调用方保证内存安全前置条件,应声明为unsafe fnwarm-up/unsafe-fn.md
反直觉点非空指针 ≠ 可转换为引用,指针可能指向任意比特同上
两条设计路径泛型场景下穷举防御不可行,责任转移更合理同上
unsafe 两角色创建(unsafe fn/unsafe trait)vs 使用(unsafe 块/unsafe implintroduction/two-roles.md
责任归属Safe Rust → 编译器;Unsafe Rust → 程序员introduction/responsibility-shift.md
文档义务无安全需求文档的 unsafe API 是不完整的introduction/two-roles.md
实战参照标准库ptr::as_mutptr_to_ref的工业级实现safety-preconditions/determining.md
工作流理解前置条件 → 验证满足 → 写 SAFETY 注释 → 分级评审introduction/impact-on-workflow.md

如果你希望在课程中继续深入,推荐按以下路径推进:

  1. 热身环节其余三个示例:unsafe block、unsafe impl、unsafe trait;
  2. 概念铺垫:Why the unsafe keyword exists 与 Defining Unsafe Rust(Unsafe Rust 是 Safe Rust 的超集,解引用裸指针是标准库实现VecBox的根基);
  3. 前置条件的系统化学习:safety-preconditions 系列,包括如何从文档与源码中确定前置条件、常见的语义前置条件等。

一句话总结unsafe函数是 Rust 中"把编译器无法验证的合法性义务显式交给调用方"的机制——ptr_to_ref这个看似平凡的例子,浓缩了 unsafe Rust 设计的全部核心思想:前置条件、责任转移、文档义务与 SAFETY 注释。

【免费下载链接】comprehensive-rustThis is the Rust course used by the Android team at Google. It provides you the material to quickly teach Rust.项目地址: https://gitcode.com/GitHub_Trending/co/comprehensive-rust

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/11 0:03:59

怀化家政AI短视频:家政服务线上获客

来源&#xff1a;唐sirAI&#xff08;www.tangsir.cc&#xff09; | 电话&#xff1a;18874530691━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━在怀化家政行业竞争日益激烈的今天&#xff0c;如何低成本、高效率地进行品牌推广&#xff…

作者头像 李华
网站建设 2026/9/11 0:01:26

HuffPost新闻数据集解析:JSONL加载与时间感知分类实战

简介&#xff1a;本资源为新闻文本分析与NLP建模实践必备的数据集&#xff0c;面向自然语言处理初学者、数据科学学习者及新闻推荐系统开发者。数据集源自HuffPost 2012–2018年真实新闻标题&#xff0c;共约20万条&#xff0c;涵盖政治、体育、娱乐等多元类别标签&#xff0c;…

作者头像 李华
网站建设 2026/9/10 23:57:01

永磁同步电机无位置传感器控制:IF开环启动到SMO闭环的仿真与实现

简介&#xff1a;面向电机控制与嵌入式开发者的无传感器滑模观测器&#xff08;SMO&#xff09;完整实现资料&#xff0c;覆盖从Simulink建模仿真、代码生成到开发板实际运行的整个链路。内容按反电动势观测、LPF低通滤波、角度与速度计算、自适应滤波及角度补偿的顺序逐步展开…

作者头像 李华