本文翻译自rust社区的命名公约(Naming)
命名公约
一般来说,Rust 倾向于用大驼峰命名法(UpperCamelCase)来命名类名、结构体名、类型名等在编程中表示类或类型的标识符,而蛇形命名法(snake_case)用于命名变量、函数、方法、文件名等在编程中表示标识符的方式。详细情况如下:
Item | Convention |
---|---|
Crates | unclear |
Modules | snake_case |
Types | UpperCamelCase |
Traits | UpperCamelCase |
Enum variants | UpperCamelCase |
Functions | snake_case |
Methods | snake_case |
General constructors | new or with_more_details |
Conversion constructors | from_some_other_type |
Macros | snake_case! |
Local variables | snake_case |
Statics | SCREAMING_SNAKE_CASE |
Constants | SCREAMING_SNAKE_CASE |
Lifetimes | short lowercase, usually a single letter: 'a, 'de, 'src |
Type parameters | concise UpperCamelCase, usually single uppercase letter: T |
Features | unclear but see C-FEATURE |
在UpperCamelCase
中,复合词的首字母缩写和缩略词算作一个单词:使用Uuid
而不是UUID
,使用Usize
而不是USize
,使用Stdin
而不是StdIn
。在snake_case中,首字母缩写和缩略词都使用小写:is_xid_start
。
在snake_case
或SCREAMING_SNAKE_CASE
中,“单词”不应该只包含一个字母,除非它是最后一个“单词”。所以,我们使用btree_map
而不是b_tree_map
,但是使用PI_2
而不是PI2
。
Crate
名称不应该使用-rs
或-rust
作为后缀或前缀。每个crate
都是Rust
!不需要不断提醒用户这一点,这样做没有意义。
标准库中的示例
下面将介绍一些实例,帮助更加容易得理解公约 。
临时转换命名遵循 as_、to_、into_ 的约定
转换应作为方法提供,名称前缀如下:
前缀 | 代价 | 所有权 |
---|---|---|
as_ | 零成本 | borrowed -> borrowed |
to_ | 开销很大 | borrowed -> borrowed |
borrowed -> owned (non-Copy types) | ||
owned -> owned (Copy types) | ||
into_ | 不确定 | owned -> owned (non-Copy types) |
举个例子:
str::as_bytes()
方法将一个str
视为 UTF-8 字节的切片视图,这是零代价的。输入是一个借用的&str
,输出是一个借用的&[u8]
。Path::to_str()
方法在操作系统路径的字节上执行了一个开销很大的 UTF-8 检查。输入和输出都是借用的。将其命名为as_str
是不正确的,因为该方法在运行时具有昂贵的成本。str::to_lowercase()
方法生成str
的 Unicode 正确的小写等价物,这涉及对字符串的字符进行迭代,并可能需要内存分配。输入是一个借用的&str
,输出是一个拥有的String
。f64::to_radians()
方法将一个浮点数从度转换为弧度。输入是f64
。不需要传递引用&f64
,因为f64
的复制成本很低。将该函数命名为into_radians
会产生误导,因为输入并没有被消耗。String::into_bytes()
方法提取了String
的底层Vec<u8>
,这是零代价的。它接收一个String
的所有权,并返回一个有所有权的Vec<u8>
。BufReader::into_inner()
方法接收一个缓冲读取器的所有权,并提取出底层的读取器,这是零成本的。缓冲区中的数据将被丢弃。BufWriter::into_inner()
方法接收一个缓冲写入器的所有权,并提取出底层的写入器,这可能需要刷新缓冲数据,操作的开销很大。
Getter的命名约定
除了少数例外情况外,在Rust代码中不使用"get_"前缀来命名获取器(getter)。
#![allow(unused)]
fn main() {
pub struct S {
first: First,
second: Second,
}
impl S {
// Not get_first.
pub fn first(&self) -> &First {
&self.first
}
// Not get_first_mut, get_mut_first, or mut_first.
pub fn first_mut(&mut self) -> &mut First {
&mut self.first
}
}
}
get的命名仅在存在单个明显可通过getter合理获取的对象时使用。例如,Cell::get 用于访问 Cell 的内容。
对于需要进行运行时验证(如边界检查)的getter,可以考虑添加不安全的_unchecked变体。通常,这些变体的签名如下所示。
#![allow(unused)]
fn main() {
fn get(&self, index: K) -> Option<&V>;
fn get_mut(&mut self, index: K) -> Option<&mut V>;
unsafe fn get_unchecked(&self, index: K) -> &V;
unsafe fn get_unchecked_mut(&mut self, index: K) -> &mut V;
}
getter和conversion之间的区别可能是微妙的,而且并不总是明确的。例如,TempDir::path 可以理解为获取临时目录的文件系统路径的 getter,而 TempDir::into_path 是将删除临时目录的责任转移给调用者的转换器。尽管 path 是一个getter,将其称为 get_path 或 as_path 是不正确的。
再看下其他的例子
- std::io::Cursor::get_mut
- std::ptr::Unique::get_mut
- std::sync::PoisonError::get_mut
- std::sync::atomic::AtomicBool::get_mut
- std::collections::hash_map::OccupiedEntry::get_mut
- <[T]>::get_unchecked
集合迭代器命名约定
集合上产生迭代器的方法遵循 iter、iter_mut、into_iter(C-ITER)的命名规则。
对于元素类型为U的容器,迭代器方法的命名应为:
#![allow(unused)]
fn main() {
fn iter(&self) -> Iter // Iter implements Iterator<Item = &U>
fn iter_mut(&mut self) -> IterMut // IterMut implements Iterator<Item = &mut U>
fn into_iter(self) -> IntoIter // IntoIter implements Iterator<Item = U>
}
这个准则适用于在概念上是同质集合的数据结构。以str类型为反例,它是一系列保证为有效UTF-8的字节片段。这个概念上更为微妙,因此不提供iter/iter_mut/into_iter一组迭代器方法,而是提供str::bytes和str::chars,再按照字符数组迭代。
这个准则仅适用于方法,而不适用于函数。例如,来自url crate的percent_encode返回一个迭代器,用于迭代百分比编码的字符串片段。使用iter/iter_mut/into_iter约定并不会增加清晰度。
看下标准库的命名会更加清楚:
- Vec::iter
- Vec::iter_mut
- Vec::into_iter
- BTreeMap::iter
- BTreeMap::iter_mut
迭代器类型的命名与生成它们的方法匹配
一个名为into_iter()的方法应该返回一个名为IntoIter的类型,其他返回迭代器的方法也是如此。
这个准则主要适用于方法,但通常也适用于函数。例如,来自url crate的percent_encode函数返回一个名为PercentEncode的迭代器类型。
这些类型名称在加上所属模块的前缀时最有意义,例如vec::IntoIter。
看下标准库的实例
- Vec::iter 返回Iter
- Vec::iter_mut 返回IterMut
- Vec::into_iter 返回IntoIter
- BTreeMap::keys 返回Keys
- BTreeMap::values 返回Values
特性名称不包含占位符
在Cargo特性的名称中不要包含没有任何意义的词语,例如use-abc或with-abc。直接将特性命名为abc。
这在Rust标准库有可选依赖的crate中最常见。正确做法是:
# 在Cargo.toml中
[features]
default = ["std"]
std = []
// 在lib.rs中
#![cfg_attr(not(feature = "std"), no_std)]
不要将特性命名为use-std或with-std,也不要使用任何与std无关的创意名称。这个命名约定与Cargo为可选依赖项推断的隐式特性的命名方式一致。考虑一个crate x,它可选择依赖于Serde和Rust标准库:
[package]
name = "x"
version = "0.1.0"
[features]
std = ["serde/std"]
[dependencies]
serde = { version = "1.0", optional = true }
当我们依赖于x时,可以通过features = [“serde”]来启用可选的Serde依赖项。类似地,我们可以通过features = [“std”]来启用可选的标准库依赖项。Cargo为可选依赖项推断的隐式特性被称为serde,而不是use-serde或with-serde,因此我们希望显式特性也遵循相同的方式。
作为相关说明,Cargo要求特性是可添加的,因此no-abc这样带有否定意义的特性名称几乎永远都是不正确的。
词序保持一致
以下是一些标准库中的错误类型:
- JoinPathsError
- ParseBoolError
- ParseCharError
- ParseFloatError
- ParseIntError
- RecvTimeoutError
- StripPrefixError
所有这些都使用动词-宾语-错误的词序。如果我们要添加一个表示地址解析失败的错误,为了保持一致性,我们应该将其命名为动词-宾语-错误的顺序,例如ParseAddrError,而不是AddrParseError。
具体的词序选择并不重要,但要注意在crate内保持一致性,并与标准库中类似功能保持一致。