Skip to content

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_
  1. str::as_bytes()

用于查看 UTF-8 字节的 str 切片,这是无内存代价的(不会产生内存分配)。 传入值是 &str 类型,输出值是 &[u8] 类型。

  • to_
  1. Path::to_str

对操作系统路径进行 UTF-8 字节检查,开销昂贵。

虽然输入和输出都是借用,但是这个方法对运行时产生不容忽视的代价。

  1. str::to_lowercase()

生成正确的 Unicode 小写字符,

涉及遍历字符串的字符,可能需要分配内存。

输入值是 ``类型,输出值是 String 类型。

  1. f64::to_radians()

把浮点数的角度制转换成弧度制。

输入和输出都是 f64 。没必要传入 &f64 ,因为复制f64花销很小。 但是使用 into_radians 名称就会具有误导性,因为输入数据没有被消耗。

  • into_
  1. String::into_bytes()

String 提取出背后的 Vec<u8> 数据,这是无代价的。 它转移了 String 的所有权,然后返回具有所有权的 Vec<u8>

  1. BufReader::into_inner()

转移了 buffered reader 的所有权,取出其背后的 reader ,这是无代价的。 存于缓冲区的数据被丢弃了。

  1. BufWriter::into_inner()

转移了 buffered writer 的所有权,取出其背后的 writer ,这可能以很大的代价刷新所有缓存数据。

如果类型转换方法返回的类型具有 mut 修饰,那么这个方法的名称应如同返回类型组成部分的顺序那 样,带有 mut。

比如Vec::as_mut_slice 返回 &mut [T] 类型,这个方法的功能正如其名称所述,所以这个名称优于 as_slice_mut