☰
Rust RFC 0199 解读:按值、按引用与按可变引用变体的命名约定(ownership variants)
2026/10/15 2:31:15 网站建设 项目流程
  • 文档

【免费下载链接】rfcs

RFCs for changes to Rust

项目地址:https://gitcode.com/gh_mirrors/rf/rfcs
点击查看免费下载

导读

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缩写决定:

类型名方法中的写法
&strstr
&[T]slice
&mut [T]mut_slice
&[u8]bytes
&Tref
&mut Tmut
*const Tptr
*mut Tmut_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 形态的源头。在设计自己的库时,可以遵循同样的判断顺序:

  1. 先确定"默认变体"是借用还是按值;
  2. 非默认变体一律用后缀标记(_mut、_ref、_move/_into_);
  3. 若mut属于类型名的一部分,保持其在类型中的形态;
  4. 对于产生迭代器的方法,优先使用iter、iter_mut、into_iter三件套。

这样写出的 API 既便于按字母序分组浏览,也符合 Rust 社区十年来沉淀下来的直觉与记忆模式。

  • 文档

【免费下载链接】rfcs

RFCs for changes to Rust

项目地址:https://gitcode.com/gh_mirrors/rf/rfcs
点击查看免费下载

相关推荐

上一篇:高效科研必备:Pubmed-Batch-Download批量文献下载工具全解析
下一篇:终极Vue3后台管理系统:从零实现AI驱动的数据可视化面板

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询