- 文档
【免费下载链接】rfcs
RFCs for changes to Rust
导读
Rust 中同一操作常常存在"按值(owned)""按不可变引用(borrowed)""按可变引用(mutably borrowed)"三种变体,而早期标准库对这些变体的命名各自为政:有时用mut_前缀,有时用_mut后缀,甚至出现_mut_夹在中间的写法,导致 API 极难记忆。本文以 RFC 0199(text/0199-ownership-variants.md) 为骨架,系统讲解这套最终被 Rust 采纳的"后缀式"命名规则——包括默认不可变借用时的_mut/_move后缀、默认按值时的_ref/_mut后缀,以及迭代器的into_iter特例。读完本文,你将掌握今天iter/iter_mut/into_iter、as_ref/as_mut、get_ref/get_mut等 API 命名的来源与设计取舍,并能把这套约定直接应用到自己的 crate 设计之中。
背景:为什么需要一套统一约定
RFC 0199 是一份conventions RFC(约定类 RFC),目标是"当某个操作存在按值、按引用、按可变引用三种变体时,统一其命名方式"。
在 RFC 提出时(2014 年),标准库对 mut 变体的标记方式并不一致:
- 有时用
mut_前缀; - 有时用
_mut后缀; - 偶尔还会在中间出现
_mut_。
这些不一致让 API 难以记忆。虽然每种立场都能找到支持的理由,但"统一约定"本身能带来巨大收益,而且某种程度上"我们只需要做出一个选择"——这正是 RFC 的务实出发点。
典型的例子是迭代器方法的三元组:
iter处理不可变借用数据;mut_iter处理可变借用数据;move_iter处理按值(owned)数据。
对于迭代器,默认(不带标记)的变体是"不可变借用";而在其他场景下,默认变体往往是"按值"。RFC 提出的规则取决于哪个变体是默认的,但无论哪种情况都统一使用后缀来标记非默认变体。
核心规则:后缀式命名
规则一:默认不可变借用(immutably borrowed by default)
如果foo默认使用/产生不可变借用,那么:
- 可变借用变体使用
_mut后缀,例如foo_mut; - 按值(owned)变体使用
_move后缀,例如foo_move。
不过,迭代器是一个例外:移动变体也可以被理解为一种into转换,写成into_iter。对比一下:
for x in v.into_iter() { ... } // 读起来明显更自然 for x in v.iter_move() { ... } // 生硬因此迭代器场景下的约定是into_iter,而非iter_move。
注意:该约定只覆盖迭代器的方法名,不覆盖迭代器类型名。迭代器类型的命名是后续 RFC(RFC 0344)的主题。
规则二:默认按值(owned by default)
如果foo默认使用/产生按值数据,那么:
- 不可变借用变体使用
_ref后缀,例如foo_ref; - 可变借用变体使用
_mut后缀,例如foo_mut。
例外:mut是类型名的一部分
对于可变借用变体,如果mut限定词本身就是类型名的一部分(例如as_mut_slice中的mut_slice),那么mut应该按照它在类型中出现的形态来书写(即as_mut_slice,而不是as_slice_mut)。
对类型名的引用:统一用mut缩写
当时的标准库中,有的地方写as_ref/as_mut,有的地方写get_ref/get_mut_ref。RFC 提议:普遍将mut作为mut_ref的缩写,统一成as_ref/as_mut、get_ref/get_mut这样的成对形态。
设计理由
为什么用后缀?
后缀让同组变体在视觉上更容易归拢,尤其是在按字母排序时:foo、foo_move、foo_mut、foo_ref会自然排在一起。它把注意力放在"功能本身"上,而不是修饰词上。
为什么用move?
历史上 Rust 用move来表示所有权转移,也与 C++ 的术语体系对接。其主要缺点是它没有强调"所有权"(ownership)——而这正是 Rust 当前的主叙事。但从另一个角度看,在 Rust 中所有数据都是有所有权的,所以用_owned作为限定词反而有点奇怪。
Copytrait 对任何"所有权转移"术语都构成挑战。RFC 给出的心智模型是:对于Copy数据,你是在"移动一份拷贝"(moving a copy)。
为什么用mut而不是mut_ref?
更短,且as_ref与as_mut这对名称有一种舒服的和谐感,不会让某一种引用显得比另一种更重要。
备选方案与取舍
备选一:前缀或混合限定词
给变体加前缀也是一种可能,但似乎没什么好处。当时仓库里前缀与后缀混用的情况,虽然可以用"语法学区分"(grammatical distinctions)来合理化,但这种做法过于微妙和复杂,需要很强的英语语法功底才能用好,因此被否决。
备选二:无后缀例外(一律把限定词放后缀)
本规则在mut属于类型名(如as_mut_slice)时破例;另一种做法是始终把限定词放在后缀,写成as_slice_mut。这会让 API 在某些方面更一致,但在另一些方面更不一致——转换函数将不再一致地"转录"自己的类型名。RFC 认为这或许并不算太糟(因为我们本来就经常缩写类型名),但无论如何,"方法中如何引用类型名"需要单独的约定 RFC 来规范(这一条后来正是由 RFC 0344 完成)。
备选三:用owned代替move
Rust 的整体叙事一直在向"所有权是核心概念、借用是所有权的一种较弱形式"演进,因此_owned似乎是个合理选择。但问题是:ref变体并没有说 "borrowed",所以这个选择在某种意义上并不一致;而且owned术语对来自 C++ 背景的开发者不够熟悉。
备选四:用val代替owned
另一个选项是用val或value。这个建议呼应了"按引用(by reference)/按值(by value)"的区分,与ref的配套性比move更强;缺点是它不如move或owned那么清晰、有画面感。
在仓库后续 RFC 中的落地与验证
RFC 0199 不是孤立的提案,仓库中多个后续 RFC 直接引用并落实了它的约定,可以作为我们理解这套规则如何演化的直接证据。
迭代器三件套的定型:iter/iter_mut/into_iter
RFC 0344(text/0344-conventions-galore.md) 明确写道:一个更早的 RFC(即本文所述的 RFC 199)确定了"标准"迭代器方法:iter、iter_mut、into_iter。同时它把迭代器类型名也统一为"方法名去掉 snake_case 后的 PascalCase 形式":
iter产生Iter;iter_mut产生IterMut;into_iter产生IntoIter。
这份 RFC 还总结了"方法中引用类型名"的对照表,其中正对应着 RFC 0199 的mut缩写决定:
| 类型名 | 方法中的写法 |
|---|---|
&str | str |
&[T] | slice |
&mut [T] | mut_slice |
&[u8] | bytes |
&T | ref |
&mut T | mut |
*const T | ptr |
*mut T | mut_ptr |
并说明:"这里唯一的意外是使用mut而非mut_ref来表示可变引用。这个缩写已经是相当常见的约定(例如as_ref与as_mut方法),目的是让这个非常常见的场景保持简短。"——这正是 RFC 0199 提案的直接延续。
集合 API 的标准三件套
RFC 0235(text/0235-collections-conventions.md) 在"迭代"一节为每个集合规定了标准迭代方法三元组,并要求集合类型同时为自身和(可变)引用实现IntoIterator:
fn iter(&'a self) -> Items<'a>; fn iter_mut(&'a mut self) -> ItemsMut<'a>; fn into_iter(self) -> ItemsMove;同一 RFC 还给出了IntoIteratortrait 的设计及impl Iterator for I的 blanket 实现,并展示了Vec的三个IntoIterator实现(Vec<T>、&'a Vec<T>、&'a mut Vec<T>)。这也印证了 RFC 0199 中"移动变体可以理解为into转换"的论断——into_iter因为按值消费 self,天然只产生单次迭代,与 Java 等语言中 "iterable 可无限次迭代" 的语义形成对照。
转换 trait 的成对命名
RFC 0529(text/0529-conversion-traits.md) 把as_ref/as_mut固化为一对基础转换 trait:
trait AsRef<T: ?Sized> { fn as_ref(&self) -> &T; } trait AsMut<T: ?Sized> { fn as_mut(&mut self) -> &mut T; } trait Into<T> { fn into(self) -> T; } trait From<T> { fn from(T) -> Self; }并说明前三者"镜像了我们的as/into约定":as风格的转换是"引用到引用",into风格的转换是"任意类型之间(按值消费参数)"。这正是 RFC 0199"统一用mut缩写mut_ref"的落地产物。
类型名中的mut例外:as_mut_slice
RFC 0198(text/0198-slice-notation.md) 设计切片 trait 时采用了as_mut_slice、slice_from_mut、slice_to_mut、slice_mut等名称,并特别注明:"这里的可变名称是命名约定可能变更的一部分,将在一个单独的 RFC 中描述"——即指向 RFC 0199 的例外规则:当mut是类型名(mut slice)的一部分时,按其在类型中的形态书写。可见 RFC 0199 的"例外条款"正是为了兼容这类场景而设计的。
历史过渡期的证据:mut_iter
在 RFC 0179(text/0179-and-mut-patterns.md) 中仍能看到旧命名v.mut_iter()的痕迹,这从侧面说明 RFC 0199 提出的后缀式命名(iter_mut)确实取代了早期的前缀式mut_iter,是 Rust 命名体系演进中承前启后的一环。
总结与实操建议
RFC 0199 确立的命名体系可以浓缩为一张速查表:
| 默认变体 | 非默认变体标记 |
|---|---|
默认不可变借用(foo) | 可变借用 →foo_mut;按值 →foo_move(迭代器例外:into_iter) |
默认按值(foo) | 不可变借用 →foo_ref;可变借用 →foo_mut |
mut属于类型名 | 按类型原样书写,如as_mut_slice |
mut_ref缩写 | 统一为mut,如as_ref/as_mut |
这套约定经过 RFC 0344、RFC 0235、RFC 0529 等一系列后续 RFC 的细化与落地,最终成为我们今天熟知的iter/iter_mut/into_iter、as_ref/as_mut、get_ref/get_mut等 API 形态的源头。在设计自己的库时,可以遵循同样的判断顺序:
- 先确定"默认变体"是借用还是按值;
- 非默认变体一律用后缀标记(
_mut、_ref、_move/_into_); - 若
mut属于类型名的一部分,保持其在类型中的形态; - 对于产生迭代器的方法,优先使用
iter、iter_mut、into_iter三件套。
这样写出的 API 既便于按字母序分组浏览,也符合 Rust 社区十年来沉淀下来的直觉与记忆模式。
- 文档
【免费下载链接】rfcs
RFCs for changes to Rust
相关推荐
Rust 编译器错误码 E0009 详解:同一模式中混用按值与按引用绑定的历史限制(rustc_error_codes)
Rust 编译器错误码 E0009 详解:同一模式中混用按值与按引用绑定的历史限制(rustc_error_codes) 本文基于 Rust 编译器仓库中 E0
编程语言编译器语言运行时标准库30 seconds of code 深度解读:JavaScript 到底是按值传递还是按引用传递?
30 seconds of code 深度解读:JavaScript 到底是按值传递还是按引用传递? 导读 在 JavaScript 开发中,"函数参数究竟如何
教程文档Rust RFC 151 解读:闭包按值捕获(capture by value)的设计起源与演进
Rust RFC 151 解读:闭包按值捕获(capture by value)的设计起源与演进 导读 本篇文章围绕本仓库中的 RFC 0151(text/01
文档
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考