G.NAM.02 类型转换函数命名宜遵循所有权语义
【级别】 建议
【描述】
Rust 语言依赖所有权来管理内存,所以在命名上遵循所有权语义,可以提升代码可读性和透明性,以便 从代码命名中发现内存使用的代价。
【说明】
进行特定类型转换的方法名应该包含以下前缀:
名称前缀 | 内存代价 | 所有权 | 方法中self 的类型 |
---|---|---|---|
as_ | 无代价 | borrowed -> borrowed | &self 或 &mut self |
to_ | 代价昂贵 | borrowed ->borrowed borrowed -> owned (非 Copy 类型) owned -> owned (Copy 类型) | &mut self &self (非 Copy 类型) self (Copy 类型) |
into_ | 看情况 | owned -> owned (非 Copy 类型) | self |
以 as_
和 into_
作为前缀的类型转换通常是 降低抽象层次,要么是查看背后的数据 (as
),要么是解构 (deconstruct) 背后的数据 ( into
) 。
相对来说,以 to_
作为前缀的类型转换处于同一个抽象层次,但是底层会做更多工作,比如多了内存拷贝等操作。
当一个类型用更高级别的语义 (higher-level semantics) 封装 (wraps) 一个内部类型时,应该使用 into_inner()
方法名来取出被封装类型的值。
【反例】
Rust
fn main() {
struct X();
static S: &str = "str";
impl X {
// 不符合:as_ 命名的方法不应获取 self 的所有权
fn as_str(self) -> &'static str {
S
}
// 不符合:当返回值不是借用,to_ 命名的方法不应使用 &mut self 作为参数
fn to_my_string(&mut self) -> String { S.to_string()
}
// 不符合:into_ 通常需要获取 self 的所有权
fn into_my_string(&self) -> String {
S.to_string()
}
}
}
【正例】
Rust
fn main() {
struct X();
static S: &str = "str";
impl X {
// 符合:as_ 命名的方法只借用 self
fn as_str(&self) -> &'static str {
S
}
// 符合:返回值不是借用,且 self 对应的类型不支持 Copy 时,to_ 命名的方法则使用 &self 作为参数
fn to_my_string(&self) -> String { S.to_string()
}
// 符合:into_ 命名的方法获取了 self 的所有权
fn into_my_string(self) -> String { S.to_string()
}
}
}
其他标准库示例:
- as_
用于查看 UTF-8 字节的 str
切片,这是无内存代价的(不会产生内存分配)。 传入值是 &str
类型,输出值是 &[u8]
类型。
- to_
对操作系统路径进行 UTF-8 字节检查,开销昂贵。
虽然输入和输出都是借用,但是这个方法对运行时产生不容忽视的代价。
生成正确的 Unicode 小写字符,
涉及遍历字符串的字符,可能需要分配内存。
输入值是 ``类型,输出值是 String
类型。
把浮点数的角度制转换成弧度制。
输入和输出都是 f64
。没必要传入 &f64
,因为复制f64
花销很小。 但是使用 into_radians
名称就会具有误导性,因为输入数据没有被消耗。
- into_
从 String
提取出背后的 Vec<u8>
数据,这是无代价的。 它转移了 String
的所有权,然后返回具有所有权的 Vec<u8>
。
转移了 buffered reader 的所有权,取出其背后的 reader ,这是无代价的。 存于缓冲区的数据被丢弃了。
转移了 buffered writer 的所有权,取出其背后的 writer ,这可能以很大的代价刷新所有缓存数据。
如果类型转换方法返回的类型具有 mut
修饰,那么这个方法的名称应如同返回类型组成部分的顺序那 样,带有 mut。
比如Vec::as_mut_slice
返回 &mut [T]
类型,这个方法的功能正如其名称所述,所以这个名称优于 as_slice_mut
。