第2章 互操作性
5 分钟阅读
译文 · 基于 Rust API Guidelines
原文链接: https://rust-lang.github.io/api-guidelines/interoperability.html
互操作性
类型积极实现常用 trait (C-COMMON-TRAITS)
Rust 的 trait 系统不允许 orphan(孤儿)实现:大致来说,每个 impl 必须位于定义该 trait 的 crate,或定义实现类型的 crate 中。
因此,定义新类型的 crate 应积极实现所有适用的常用 trait。
要理解原因,请考虑以下情形:
- Crate
std定义了 traitDisplay。 - Crate
url定义了类型Url,但未实现Display。 - Crate
webapp同时从std和url导入,
webapp 无法为 Url 添加 Display,因为它两者都未定义。(注:newtype 模式可以提供一种高效但不方便的变通办法。)
从 std 中最重要、应实现的常用 trait 包括:
请注意,类型同时实现 Default 以及一个无参的 new 构造函数是常见且符合预期的。
new 是 Rust 中的构造函数约定,用户期望它存在,因此如果基本构造函数不需要参数是合理的,
那就应当提供,即使它在功能上与 default 完全相同。
转换使用标准 trait From、AsRef、AsMut (C-CONV-TRAITS)
在合理的地方,应实现以下转换 trait:
以下转换 trait 永远不应实现:
这些 trait 基于 From 和 TryFrom 有 blanket impl。应实现后者。
标准库中的示例
From<u16>为u32实现,因为较小的整数总是可以转换为较大的整数。From<u32>不 为u16实现,因为若整数过大,转换可能无法完成。TryFrom<u32>为u16实现,若整数过大无法放入u16则返回错误。From<Ipv6Addr>为IpAddr实现,后者是可同时表示 v4 与 v6 IP 地址的类型。
集合实现 FromIterator 与 Extend (C-COLLECT)
FromIterator 与 Extend 使集合能方便地与下列迭代器方法一起使用:
FromIterator 用于从迭代器创建包含其元素的新集合,而 Extend 用于将迭代器中的元素追加到已有集合。
标准库中的示例
Vec<T>同时实现了FromIterator<T>与Extend<T>。
数据结构实现 Serde 的 Serialize、Deserialize (C-SERDE)
扮演数据结构角色的类型应实现 Serialize 与 Deserialize。
从明显是数据结构的东西,到明显不是的东西,中间存在连续谱与灰色地带。LinkedHashMap
与 IpAddr 是数据结构。有人想从 JSON 文件读入 LinkedHashMap 或 IpAddr,
或通过 IPC 发送到另一进程,这完全合理。LittleEndian 不是数据结构。
它是 byteorder crate 用来在编译期针对特定字节序做优化的标记,事实上 LittleEndian
的实例在运行时永远不会存在。这些是界限清晰的例子;更模糊的情形必要时可在 #rust 或 #serde
IRC 频道寻求评估帮助。
若 crate 并非因其他原因已依赖 Serde,可将 Serde 的 impl 放在 Cargo cfg 之后。这样下游库 仅在需要这些 impl 存在时才承担编译 Serde 的成本。
为与其他基于 Serde 的库保持一致,Cargo cfg 的名称应简单地为 "serde"。不要使用
"serde_impls" 或 "serde_serialization" 等不同的 cfg 名称。
不使用 derive 时,规范实现如下:
| |
| |
使用 derive 时:
| |
| |
类型在可能时是 Send 和 Sync (C-SEND-SYNC)
在操作原始指针的类型中,请警惕你的类型的 Send 与 Sync 状态是否准确反映其线程安全特性。
类似下面的测试有助于捕获类型是否实现 Send 或 Sync 的无意回退。
| |
错误类型有意义且行为良好 (C-GOOD-ERR)
错误类型是指你的 crate 任一公开函数所返回的 Result<T, E> 中的类型 E。错误类型应始终实现
std::error::Error trait,这是像 error-chain 这样的错误处理库对不同错误类型进行抽象的机制,
也允许该错误被用作另一错误的 source()。
此外,错误类型应实现 Send 与 Sync trait。非 Send 的错误无法由 thread::spawn
启动的线程返回。非 Sync 的错误无法通过 Arc 跨线程传递。这些是多线程应用中基本错误处理的常见要求。
Send 与 Sync 对于使用 std::io::Error::new 将自定义错误打包进 IO 错误也很重要,
该方法要求 trait bound 为 Error + Send + Sync。
需要特别留意本指南的一处是返回 Error trait 对象的函数,例如 reqwest::Error::get_ref。
通常对调用方最有用的是 Error + Send + Sync + 'static。加上 'static 可使该 trait 对象
与 Error::downcast_ref 一起使用。
永远不要用 () 作为错误类型,即使没有有用的额外信息可供错误携带。
()未实现Error,因此无法与error-chain等错误处理库一起使用。()未实现Display,因此若用户想因该错误而失败,需要自行编写错误消息。- 对决定
unwrap()该错误的用户而言,()的Debug表示毫无帮助。 - 下游库为其错误类型实现
From<()>在语义上没有意义,因此不能将()作为错误类型与?运算符一起使用。
相反,应定义专属于你的 crate 或个别函数的有意义错误类型。提供适当的 Error 与 Display impl。
若没有有用的信息可供错误携带,可实现为单位结构体。
| |
错误类型的 Display 表示所给出的错误消息应为小写、无尾随标点,且通常简洁。
不应实现 Error::description()。它已被弃用,用户应始终使用 Display 而非 description() 来打印错误。
标准库中的示例
- 从字符串解析 bool 失败时返回
ParseBoolError。
错误消息示例
- “unexpected end of file”
- “provided string was not `true` or `false`”
- “invalid IP address syntax”
- “second time provided was later than self”
- “invalid UTF-8 sequence of {} bytes from index {}”
- “environment variable was not valid unicode: {:?}”
二进制数值类型提供 Hex、Octal、Binary 格式化 (C-NUM-FMT)
这些 trait 控制类型在 {:X}、{:x}、{:o} 与 {:b} 格式说明符下的表示。
对你会考虑进行 | 或 & 等位运算的任何数值类型实现这些 trait。这对 bitflag 类型尤其合适。
像 struct Nanoseconds(u64) 这样的数值量类型通常不需要这些。
泛型读写函数按值接受 R: Read 与 W: Write (C-RW-VALUE)
标准库包含以下两个 impl:
| |
这意味着任何按值接受 R: Read 或 W: Write 泛型参数的函数,必要时都可用可变引用调用。
在此类函数的文档中,简要提醒用户可以传入可变引用。Rust 新手常在此卡住。他们可能已经打开了文件,
想从中读取多段数据,但读取一段的函数按值消费了 reader,于是他们束手无策。解决办法是利用上述
impl 之一,将 &mut f 而非 f 作为 reader 参数传入。