一篇讲述 Rust API 设计原则的经典老文,虽然老但仍然值得阅读。前几天有事情把它从旮旯里翻出来了,虽然是经典老文但我估计很多人都没有读过,最近闲着没事干脆用我的辣鸡英语水平翻译一下,也算为 Rust 中文社区做一点贡献。
原文见:https://deterministic.space/elegant-apis-in-rust.html
第一次在论坛发博客,不知道论坛的 markdown 支持怎么样,也不知道支不支持修改。如果阅读有问题的话请访问:https://www.aloxaf.com/2019/11/elegant_apis_in_rust/
设计优雅的 Rust 库 API
在选择一门编程语言时,是否拥有简洁易用的库也是一个重要的考量因素。这篇文章会教授你如何为你的 Rust 库编写优雅的 API。(不过文中的许多观点也适用于其他语言。)
你也可以观看我在 Rustfest 2017 上的演讲!
什么是优雅的 API
方法名清晰易懂,以让调用了这个 API 的代码易于阅读。
有规律、可猜测的方法名在使用 API 时也很有用,可以减少阅读文档的需求。
每个 API 都有至少要有文档和一小段示例代码。
用户几乎不需要编写样板代码(boilerplate code)来使用这个 API,因为
-
它广泛接受各种输入类型(当然类型转换是显式的)
并且也有足以应付大部分常用情况的一键 API
充分利用类型来防止逻辑错误,但不会太妨碍使用。
返回有意义的错误,并且在文档中注明会导致 panic 的情况。
技术
一致的命名
有一些 Rust RFC 描述了标准库的命名方案。你也应该遵循它们,以让用户能迅速上手使用你的库。
RFC 199 解释说应该使用
mut
、move
或ref
作为后缀,来根据参数的可变性区分方法。RFC 344 定义了一些有意思的约定,比如:
-
如何在方法名称中引用类型名称(如
&mut [T]
变成mut_slice
、*mut T
变成mut ptr
),如何命名返回迭代器的方法,
getter 方法应该被命名为
field_name
而 setter 方法应该被命名为set_field_name
,如何命名 trait:“优先选择(及物)动词、名词,然后是形容词;避免语法后缀(如 able)”,而且“如果这个 trait 只有一个主要方法,可以考虑用方法名称来命名 trait 本身”,
RFC 430 描述了一些通用的大小写约定(总结:
CamelCase
用于类型级别,snake_case
用于变量级别)。RFC 445 希望你为扩展 trait(extension trait)添加
Ext
后缀。
更多的方法名称约定
除了 RFC 199 和 RFC 344 (见上)规定的以外,还有一些其他的关于如何选择方法名称的约定,目前还没有在 RFC 中提及。这些约定大部分都在旧的 Rust 风格指南和 @llogiq 的文章 Rustic Bits 以及 clippy 的 wrong_self_convention
检测项中提到了。这里总结一下。
方法名称 | 参数 | 备注 | 举例 |
---|---|---|---|
new |
无 self,通常 >= 1 [^1] | 构造器,另参见 Default |
Box::new 、std::net::Ipv4Addr::new |
with_... |
无 self,>= 1 | 其他构造器 | Vec::with_capacity 、regex::Regex::with_size_limit |
from_... |
1 | 参见转换 trait(conversion traits) | String::from_utf8_lossy |
as_... |
&self |
无开销的转换,返回数据的一个视图(view) | str::as_bytes 、uuid::Uuid::as_bytes |
to_... |
&self |
昂贵的转换 | str::to_string 、std::path::Path::to_str |
into_... |
self (消耗) |
可能昂贵的转换,参见 转换 trait(conversion traits) | std::fs::File::into_raw_fd |
is_... |
&self (或无) |
期望返回 bool |
slice::is_empty 、Result::is_ok 、std::path::Path::is_file |
has_... |
&self (或无) |
期望返回 bool |
regex_syntax::Expr::has_bytes |
文档测试
编写带有示例代码的文档可以展示 API 的用法而且还能获得自动测试——一石二鸟。详见第一版 TRPL(The Rust Programming Language)的文档一节。
/// 使用魔法操作数字
///
/// # 示例
///
/// ```rust
/// assert_eq!(min( 0, 14), 0);
/// assert_eq!(min( 0, -127), -127);
/// assert_eq!(min(42, 666), 42);
/// ```(由于 hexo markdown 渲染辣鸡,此处加点文字避免被渲染为单独代码块)
fn min(lhs: i32, rhs: i32) -> i32 {
if lhs < rhs { lhs } else { rhs }
}
你还可以使用 #![deny(missing_docs)]
来强制保证每个公开 API 都有文档。你可能也会对我的这篇提