# API 设计:输入输出、类型、trait 与 API 属性 提炼自指南的 Function inputs and outputs / Types / Traits / Concurrency / Macros / Other Rust features 章节。 总纲:**库代码**(供其他项目复用)必须遵守 API 设计规则;**应用代码**(单一程序内部)作为强烈建议,优先简单直接、避免过早泛化与抽象。 ## 函数输入与输出 ### 不可变借用参数取最灵活的形态 传"数据本身"的引用,不传智能指针/容器/包装类型的引用: | 用 | 不用 | 原因 | |---|---|---| | `&T` | `&Box` | 借 box 里的数据 | | `&[T]` | `&Vec` | 接受任意切片而非特定容器 | | `&str` | `&String` | 接受字符串切片而非 owned 字符串的引用 | | `Option<&T>` | `&Option` | 借 Option 里的数据 | 例外:预期非线性字符数据(Rope/Cord)。对应 clippy:`borrowed_box`、`ptr_arg`。 理由:不绑定具体容器/包装的引用让函数更通用,调用方无需先复制。 ### 返回值:迭代器 vs 所有权集合 - 库代码尽量返回迭代器(具体类型或 `impl Iterator`)而非 `Vec`。 - 应用代码:返回 `Vec` 没问题,简单优先。 返回 `Vec` 更合适的情形:多数调用方需要 owned vector;实现本来就构造 vector;返回值需要随机索引;算法更适合 `for` 循环且强制分配影响不大;函数目的就是创建新集合;返回已存在的 vector。 ### 参数:迭代器 vs 切片 - 高复用性的库代码只需遍历输入时,接受迭代器或 `impl IntoIterator`(更灵活、免拷贝、可组合)。 - 应用代码:接受 `&[T]` 更简单即可。 ### 接受 `Into` - 库 API 用户实参类型多样、调用点体验优先时,考虑 `impl Into` 代替 `T`(如 `impl Into` 可直接收 `String` 或 `&str` 字面量)。权衡泛型的代码膨胀与类型推断复杂度,不是多多益善。 - 应用代码几乎不用 `impl Into` 参数。 ### 参数用 `enum` 不用 `bool` 参数用途不明显时用 `enum`。坚持用 `bool` 时,在参数列表加行内注释说明用途。 ### 错误上报:`Option` / `Result` / `panic!` - `Option`:失败原因显而易见、更多细节无益。如 `checked_add` 的整数溢出。 - `Result`:可恢复的失败,调用方需要上下文区分失败模式并恢复,常源于外部因素(I/O、网络)。如 `open_file` 需区分 NotFound 与 PermissionDenied。 - `panic!`:报告 bug 与被违反的不变量(如索引越界)——应在代码里修,不是运行时处理;粗粒度恢复或可借助 `catch_unwind`(其使用限制见 implementation.md)。 注:部分代码库选择完全不用 panic,连被破坏的不变量也要求用 `Result` 报告。 例外:面向开发者的二进制可用 panic 报非致命错误;测试断言可用 panic。 ## 类型 ### newtype vs 类型别名 以下情况用 newtype:类型有显著语义差异;值得拥有自己的 API;想保留日后更换底层类型的自由(尤其不控制全部用例时);类型安全需要(`Date` 内部用 newtype 区分 `Day`/`Month`/`Year`)。 ### 命名字段优先于元组 元组下标(`.0`、`.1`)很快会看不懂。例外:单字段 newtype 元组结构体;极局部、不会混淆的场景。 ## trait ### 实现时机 低层库代码应预判用户需求、主动实现;应用代码按需添加。 ### 实现标准库 trait 而非自定义方法 方法与标准库 trait 的目的和行为一致时实现该 trait:`impl FromStr for Foo` 而不是在 `Foo` 上写 `fn from_str(&str) -> Self`。为可发现性可以两者都保留。对应 clippy `should_implement_trait`。 ### 能 `#[derive()]` 就不用手写 ### 泛型优先于 `dyn Trait` 库中函数参数用 `T: Trait` / `impl Trait` 优于 `dyn Trait`。例外:代码体积敏感。 理由:dyn 有胖指针与 vtable 开销、阻碍内联等编译器优化;部分 trait 不 dyn 兼容(含泛型函数、无 `self` 的关联函数、返回 `Self` 的 trait)。 ### 何时定义 trait 只为以下目的定义 trait:泛型编程(`T: Trait` 约束);接受或返回 `impl Trait` / `dyn Trait`;定义扩展 trait。其余情况用固有实现(inherent impl)。**不要用 trait 来组织代码。** ### 扩展 trait(extension trait) 给其他 crate 的类型加方法时用扩展 trait——唯一目的是给该类型加方法,不用于泛型或动态分发。命名带 `Ext` 后缀(原文记法 `~Ext`,如 `ForeignTypeExt`)。原则上由原类型的 owning team 同时维护扩展 trait。例外:预期广为人知、价值巨大的基础库/框架扩展。注意:作为输入约束的 trait(如 `DateLike`,实现于一堆外部类型)不属此列。 ```rust trait ForeignTypeExt { fn additional_method(&self); } impl ForeignTypeExt for ForeignType { … } ``` 也可为所有实现某外部 trait 的类型统一实现扩展 trait,效果等同于给该外部 trait 添加默认方法: ```rust trait ForeignTraitExt { fn additional_method(&self); } impl ForeignTraitExt for T { … } ``` ### 重要 trait 的实现建议 - 公开字段的类型:考虑实现所有子字段都实现的 trait。 - 错误类型:尽量实现 `Copy`(含 `String` 等非 Copy 字段时除外)。 - `Copy`:有理由相信类型将长期保持 Copy 就实现。 - `Clone`:除表示外部资源的类型外都应实现。 - `Default`:实现了 `new()` 或有默认状态就实现(clippy `new_without_default`)。 - `PartialEq` / `Eq`:想让用户用 `==` 就实现 `PartialEq`;实现 `Eq` 注意额外的自反性要求。 - `PartialOrd` / `Ord`:同理;`Ord` 要求全序。 - map key:类型可能用作 key 或被缓存 → 实现 `Hash` + `Eq`;再有清晰全序 → 加 `Ord`。 ### 重要 `From` 实现配对显式构造方法 重要的 `From` impl 若是主要构造方式之一,配一个固有 `::new()` / `::new_from_foo()` / `::from_foo()`。 ### options 结构体实现 `Default` 全公开字段、主要作为函数"选项袋"的 struct 应实现 `Default`,以支持 `Struct { a: 0, ..Default::default() }`。 ## 并发 - 永远不要从 poisoned mutex 里取内部数据,无论用例多诱人。 - [chrome] 不用 `async` / `.await`。 - [chrome] 不通过标准库起新线程,用 sequences。 - [Fuchsia] 用 `fuchsia_sync::Mutex`,不用 `std::sync` 或 `parking_lot`。 ## 宏 API 表达优先级:常规函数/类型/trait/对象/模块 **>** `macro_rules!` 宏 **>** 其他代码生成(如 genrule)**>** `proc_macro`。 决定用宏或代码生成前权衡成本:IDE、代码搜索等工具对宏代码支持更差;宏会制造语言方言,增加跨代码库工作成本;LSC 更难更贵。 `macro_rules!` 基于 AST 展开,比文本式代码生成更不易碎。 ## API 属性与访问器 ### getter / setter 命名 - `foo()`:返回 `&T`,或 `T: Copy` 时按值返回。 - `foo_mut()`:返回 `&mut T`。 - `take_foo()`:清掉值(置 `None` 或默认态)并返回旧值。 - `set_foo()`:按值设置。 - `with_foo()` / `with_mut_foo()`:经闭包访问,`object.with_foo(|foo| …)`。 - `replace_foo()`:按值设置并返回旧值。 ```rust impl Stuff { fn foo(&self) -> &Foo { … } fn foo_mut(&mut self) -> &mut Foo { … } fn take_baz(&mut self) -> Option { … } fn set_foo(&mut self, foo: Foo) { … } fn with_bar(&self, f: impl FnOnce(&Bar) -> R) -> R { … } fn with_mut_bar(&self, f: impl FnOnce(&mut Bar) -> R) -> R { … } fn replace_foo(&mut self, foo: Foo) -> Foo { … } } ``` ### 闭包式访问器 `with_foo()` 的适用场景 - 属性在内部可变性包装里(`Mutex` / `RefCell`):闭包提供访问作用域,优于直接返回 `MutexGuard`(那会泄漏实现细节)。 - 改属性后需要跟进校验/收尾、`set_foo()` 不可用时(如属性类型昂贵或无法克隆)。 闭包的返回值通常从 `with_foo()` 透传。 需要同时提供只读与可变两个闭包访问器时,按惯例命名 `with_foo()` / `with_mut_foo()`;否则 `with_foo()` 也常用于可变访问。 ### API 成套实现 实现了 `len()` 就同时实现 `is_empty()`(clippy `len_without_is_empty`)。 ### `#[doc(hidden)]` 视为私有 不访问不属于自己代码的 `#[doc(hidden)]` 条目。 ### `const` fn 慎用 公开函数只在预期将持续保持 `const`(或承诺帮调用方迁移)时才标 `const`——`const` 改回普通函数是破坏性变更。 ### 运算符重载只用于数学/逻辑本义 不做"语法糖"式的重载。 ### 公开 enum 加 `#[non_exhaustive]` 跨团队使用的公开 API enum 标 `#[non_exhaustive]`:强制调用方 match 留通配臂(`_ => …`),未来加变体不破坏编译。 例外:enum 永不变更(描述既有本体如 `enum Color { Red, Green, Blue }`;两态等价 bool 如 `enum Visibility { Visible, Hidden }`);或承诺用 LSC 修复破坏。使用范围很小的 enum 可不遵守。clippy `exhaustive_enums`。 ### 公开字段的 struct 加 `#[non_exhaustive]` 跨团队使用、含公开字段的 struct 同理:加新字段(哪怕私有)会破坏 struct 字面量构造;`#[non_exhaustive]` 使外部只能经构造函数创建。 例外:字段描述稳定本体(如 Rectangle);承诺 LSC;需要用户能使用 FRU 语法(`Person { name: "foo", ..Default::default() }`)——与 `#[non_exhaustive]` 互斥。 ### `#[must_use]` - 函数:忽略返回值就无法正确使用时——纯函数(如 `HashMap::get()`);返回值承载错误或关键信息(返回类型本身已 `#[must_use]` 的,如 `Result`,不用重复标注)。 - 类型:将被上述函数返回的类型——错误类型(类似 `Result` 或 `StatusOr`);惰性求值类型(不取返回值就不产生副作用)。 ### 不要在 `main()` 之前运行代码 不用 `ctor` crate 之类手段。 ### unsafe 代码 没有等价安全实现时才写 unsafe。必须写时: - 封装为安全抽象,与业务逻辑尽量隔离;理想:单独 crate、除标准库外极少依赖。 - 加以 `SAFETY:` 开头的注释,解释代码为何正确。 - 给技术理由:为什么不能用等价的安全写法实现;若理由是性能,给出 benchmark。 ### 不用 unstable 特性 例外:Android 嵌入式确需的(review 后一般会批);[chrome] style arbiters 可能对整库白名单一小批有稳定路径的特性;Fuchsia 另见其指引。