
文档【免费下载链接】rfcsRFCs for changes to Rust项目地址https://gitcode.com/gh_mirrors/rf/rfcs点击查看免费下载导读本文围绕 Rust RFC 0240《Unsafe API Location》系统讲解标准库中unsafeAPI 应如何摆放哪些应作为类型的关联方法/静态函数哪些应放进raw子模块以及_unchecked、from_raw_parts等命名后缀的约定。RFC 240 是 Rust 1.0 前夕 API 稳定化运动中的一项约定类 RFC其结论深刻影响了今天std::slice::from_raw_parts、String::from_utf8_unchecked、Pin::new_unchecked等一系列 API 的最终形态。读完本文你将掌握判断 unsafe API 归属的三条核心原则、配套命名规范以及它们在现代 Rust 标准库中的实际落地情况。背景为什么 unsafe API 的位置是个问题RFC 0240 的正文位于仓库中的 text/0240-unsafe-api-location.md属于 Rust RFC 系列中典型的conventions RFC——它不引入新的语言特性而是为标准库 API 该如何组织定下规则。当时2014 年许多数据结构同时提供两种 unsafe 接口直接在类型上暴露的方法用于绕过检查或直接操作否则是私有的内部表示raw子模块中的自由函数从裸指针等底层表示构造实例。以string模块为例RFC 给出了两种形态并存的典型场景String::as_mut_vec返回字符串的Vecu8视图。它方便了基于字节表示的操作但也因此允许用户破坏字符串的 UTF-8 保证string::raw子模块中的若干自由函数如from_parts从基于裸指针的表示构造String、不检查 UTF-8 合法性的from_utf8变体等。这些函数的共同点都绕过了某个关键不变量。问题在于当时没有任何清晰、一致的规则来回答这类 API 应该做成类型上的方法/静态函数还是放进raw子模块两种形式在标准库中到处混杂。RFC 0240 的目的就是终结这种混乱。核心约定两条原则RFC 提出的约定可以浓缩为两条凡是关于某个类型的 unsafe 函数/方法——无论它是构造、解构还是修改该类型的值——都应该成为该类型上的方法或静态函数。这与安全函数/方法的摆放规则完全相同。因此string::raw::from_parts应当变成String上的静态函数。raw子模块只应用于定义显式的底层类型/表示以及这些底层类型上的方法/函数。而高低层类型之间的转换方法应当直接放在高层类型上。典型例子core::raw、sync::raw。也就是说unsafe标记本身的威慑作用就够了不需要再通过放进单独模块来进一步区分这些 API 的危险程度。两条原则带来的收益人体工学Ergonomics只要手中有一个类型的值静态函数则只需导入该类型就能直接触达 unsafe API不必额外use一个raw模块一致性与简单性Consistency and simplicityunsafe API 的摆放规则与 safe API 完全一致开发者无需记住两套规则。配套命名规范除了放哪里RFC 还为 unsafe 静态函数与方法配套了三类命名约定1. 非检查变体使用_unchecked后缀当 unsafe 函数/方法是某个安全 API 的不检查版本时用_unchecked后缀标记。RFC 以String模块为例应同时提供from_utf8与from_utf8_unchecked两个构造函数后者不检查 UTF-8 编码原先string::raw::slice_bytes与string::raw::slice_unchecked应合并为字符串上的单一slice_unchecked方法它既不检查边界也不检查 UTF-8 边界。这一约定的威力在现代标准库中随处可见例如text/0494-c_str-and-c_vec-stability.md 中废弃std::str::from_c_str的替代方案就明确写为ffi::c_str_to_bytes加上str::from_utf8或str::from_utf8_unchecked——检查与否由开发者按需选择text/2349-pin.md 中的Pin::new_uncheckedtext/1398-kinds-of-allocators.md 中的alloc_unchecked、alloc_array_unchecked、dealloc_array_unchecked。2. 涉及底层表示时使用raw当 unsafe 函数/方法生产或消费某个数据结构的底层表示时API 名字中应包含raw。from_raw_parts就是从基于指针的表示构造值的典型命名。这一命名在后续 RFC 中同样被继承并具体化text/1679-panic-safe-slicing.md 中[T]的get_unchecked/get_mut_unchecked通过SliceIndextrait 泛化到usize与各种 range 类型跳过边界检查与a[idx]的检查行为形成对照text/2580-ptr-meta.md 更是直接沿用了from_raw_parts作为*const T、*mut T、NonNullT三者的构造函数名见该文第 181 行。3. 否则考虑用名字暗示为什么 unsafe对于无法套用上述两条约定的情况考虑使用能暗示为什么这个 API 是 unsafe的名字。但也承认某些情况下其他更强的约定已经生效例如String::as_mut_vec的as_约定此时签名上的unsafe限定符加上 API 文档已经足够不必强造新名。4. 独立impl块聚拢 unsafe APIRFC 还建议某个类型的所有 unsafe 方法与静态函数应放在独立的impl块中并置于定义该类型的模块末尾。这样在 rustdoc 渲染时它们会自然聚合在一起方便查阅该建议来自社区成员 lilyball。这一实践在后续被广泛采用——例如PinT的 unsafe 构造器new_unchecked与其安全构造器new分置于不同impl块保证文档分组清晰。约定背后的设计哲学RFC 明确表达了一种视角把 API 标记为unsafe已经足以阻止它在普通场景下被误用不需要再用单独模块的方式二次惩罚它。这引出了对unsafe 编程地位的讨论反驳理由Drawbacks约定会让模块文档堆满鲜少使用的 unsafe API而raw子模块方式能整洁地把它们分组。但 RFC 认为 rustdoc 很容易被改造成默认隐藏或分离 unsafe API在改造完成前impl块分组已经能缓解问题具体到unsafe 构造函数放raw里容易找这一点RFC 认为惯用的from_前缀加上上述命名约定即使构造函数以静态函数形式直接挂在类型上也足够容易被发现更一般地这些约定让 unsafe API 与 safe API 地位平等。至于这是否算缺点取决于你对 unsafe 编程地位的哲学立场。RFC 编者注Ed note明确表态底层/unsafe 编程值得支持既然用户已经显式 opt-in就没有理由再惩罚它的人体工学。补充一点后续 text/2585-unsafe-block-in-unsafe-fn.md 从语言语义层面强化了unsafe fn是定义义务、unsafe {}块是解除义务的区分——这正好呼应了本文标记 unsafe 已足够威慑的前提调用方必须显式用unsafe块承担义务API 的位置不再承担额外威慑职能。替代方案对比RFC 记录了四个被否决或未采用的替代方案理解它们有助于把握约定取舍的边界方案 A用扩展 trait 分组 unsafe API例如String旁边配一个StringRaw扩展 trait把操作原始字符串表示的 API 全部放进去。优点依然以方法/静态函数形式提供可以用use std::string::StringRaw一键导入实现清晰分组。缺点这仍然是在unsafe标记之外进一步惩罚原始 API而 rustdoc 本可轻易提供 API 分组收益存疑。方案 Blilyball 建议raw只管不检查不变量的构造函数Userawfor functions that construct a value of the type without checking for one or more invariants.优点是不变量忽略型函数容易找到缺点是人体工学变差——必须单独导入或通过长路径引用。RFC 给出了直观的对照// 对比两者的体验 string::raw::slice_unchecked(some_string, start, end) some_string.slice_unchecked(start, end)方案 Clilyball 建议保留raw子模块结构用关联类型改善人体工学细节与利弊见 RFC PR 240 的评论链接。核心思路是保留模块划分但利用关联类型减少路径噪声。方案 D把底层表示的所有操作全部收进raw子模块当时std中没有任何模块这样做现有模块是raw里放一些自由函数、类型上放一些 unsafe 方法的混合状态本身缺乏统一原则。而把一切都搬进自由函数人体工学很差——这正是 RFC 方案要避免的极端。RFC 最终选择以类型为中心 命名约定而非任何隔离方案核心理由始终是unsafe 标记已经是足够的 opt-in 门槛。遗留问题core::raw的归属RFC 承认一个尚未解决的问题core::raw模块为若干内建类型与库类型box、闭包、slice 等提供了具有公开表示的等价结构体。这个模块很特殊——它所涉及的部分类型甚至没有对应的模块/类型声明——因此本 RFC 不强行规定它的名称或内容位置是否应改变留待 API 稳定化流程处理。后续演进证实了这一判断如 text/2580-ptr-meta.md 所述Rust 1.11 移除了std::raw::Reprtrait 与std::raw::Slice类型改以更具体、更少无差别危险的std::slice::from_raw_parts/from_raw_parts_mut加as_ptr、len等方法取代std::raw::TraitObject最终也被弃用并移除。raw子模块中定义底层表示的职能逐渐被core中明确定义的表示类型与安全的转换函数接管——这与 RFC 0240 的raw只用于定义底层表示精神完全一致。另一个典型的落地案例是 text/1044-io-fs-2.1.md 为std::os::platform::raw定下的规则可视为 RFC 0240 原则的平台化扩展每个平台在std::os下有自己的raw模块存放平台相关的定义raw模块只放类型定义不放函数绑定、方法或 trait 实现跨平台类型如所有unix平台共享的类型仅整数宽度不同者视为跨平台放在跨平台模块平台专属类型只存在于对应平台的raw模块高层类型通过MetadataExt::as_raw、as_raw_stat等方法向下降低lowering到raw类型。也就是说raw模块的角色被收敛为定义底层表示而转换方法as_raw系列作为方法挂在高层类型上——两条原则各司其职。对现代 Rust API 设计者的实践清单综合 RFC 0240 及其后续继承者可以提炼出一份可操作的检查清单归属判断unsafe 操作若关于某个类型构造/解构/修改就做成该类型的方法或静态函数与安全 API 同规则raw模块收窄仅用于定义底层表示类型转换由高层类型的方法承担命名用as_raw/from_raw_parts一类的raw标记命名后缀不检查版本的 API 加_unchecked如from_utf8_unchecked、get_unchecked、new_unchecked注意多后缀时mut放最后——text/0344-conventions-galore.md 明确给出get_unchecked_mut的例子并规定含mut的多后缀时mut置末文档组织unsafe 方法与静态函数放入独立impl块置于模块末尾借助 rustdoc 自然聚合不过度隔离除非存在强约定如as_否则名字应暗示 unsafe 的原因但不必为显眼而牺牲人体工学。遵循这套约定你的库在unsafe 能力触手可及、却又不至于误用之间取得平衡——这正是 RFC 0240 十余年来为 Rust 生态确立的默认答案。赞分享文档【免费下载链接】rfcsRFCs for changes to Rust项目地址https://gitcode.com/gh_mirrors/rf/rfcs点击查看免费下载相关推荐comprehensive-rust 命名规范精讲by 后缀 —— 自定义比较器与投影函数的命名约定comprehensive rust 命名规范精讲 by 后缀 —— 自定义比较器与投影函数的命名约定 在 Google Android 团队的 Rust 课文档教程fp-ts 代码约定与后缀命名规范完全指南读懂 C/E/K/T/W 后缀与模块结构fp ts 代码约定与后缀命名规范完全指南读懂 C / E / K / T / W 后缀与模块结构 本指南以 fp ts 官方《Code Convention开发工具Woodpecker 数据库命名约定复数表名与无前缀列的规范及其在源码中的落地Woodpecker 数据库命名约定复数表名与无前缀列的规范及其在源码中的落地 本文以 Woodpecker 官方开发文档 docs/versioned_dCI/CDDevOps上一篇Kornia YUV422 转换中的色度平面形状校验yuv422_to_rgb 如何用 ShapeError 取代裸 RuntimeError下一篇在 CodeBurn 中追踪 Warp Agent 用量warp.sqlite 解析、成本估算与排障指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考