news 2026/7/24 22:44:36

【Rust中级教程】2.11. API设计原则之受约束性(constrained) Pt.2:封闭trait(sealed trait)、重新导出(re-exports)、自动trait

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
【Rust中级教程】2.11. API设计原则之受约束性(constrained) Pt.2:封闭trait(sealed trait)、重新导出(re-exports)、自动trait

2.11. API设计原则之受约束性(constrained) Pt.2:封闭trait(sealed trait)、重新导出(re-exports)、自动trait(auto-trait)

2.11.1. trait实现

Rust的一致性规则禁止了对某个trait为某类型进行多重实现。

通常情况下,以下与trait相关的操作会产生破坏性变更:
- 为现有trait添加Blanket Implementation(详见 1.17.2. 泛实现)通常是破坏性变更
- 为现有类型实现外部trait,或为外部类型实现现有trait
- 移除trait实现(为新的类型实现trait不会产生破坏性变更

大多数到现有trait的更改也是破坏性变更,例如:
- 为现有的trait改变方法的签名
- 添加新方法(如果新方法有默认实现就不产生破坏性变更


为任何类型实现任何trait都要小心

多提一嘴,为任何类型实现任何trait都要小心。

看个例子:

lib.rs:

pub struct Unit; // 定义 trait pub trait Foo1 { fn foo(&self); } impl Foo1 for Unit { fn foo(&self) { println!("foo1"); } }

main.rs:

use constrained::{Foo1, Unit}; // 定义 trait trait Foo2 { fn foo(&self); } // 为 Unit 实现 Foo2 trait impl Foo2 for Unit { fn foo(&self) { println!("foo2"); } } // 运行主函数 fn main() { Unit.foo(); }

输出:

error[E0034]: multiple applicable items in scope --> src/main.rs:14:10 | 14 | Unit.foo(); | ^^^ multiple `foo` found | = note: candidate #1 is defined in an impl of the trait `Foo1` for the type `Unit` note: candidate #2 is defined in an impl of the trait `Foo2` for the type `Unit` --> src/main.rs:8:5 | 8 | fn foo(&self) { | ^^^^^^^^^^^^^ help: disambiguate the method for candidate #1 | 14 - Unit.foo(); 14 + Foo1::foo(&Unit); | help: disambiguate the method for candidate #2 | 14 - Unit.foo(); 14 + Foo2::foo(&Unit); |

这么写是会报错的,注意到错误在哪里了吗?问题出在foo方法,main.rs和lib.rs各有一个Foo2Foo1trait,而两者都有foo这个方法,Unit结构体既实现了Foo1也实现了Foo2。在main.rs中使用foo方法时编译器就不清楚到底改使用哪个trait上的foo方法。

这就是为什么为任何类型实现任何trait都要小心——实现trait有可能在不经意间造成的破坏性变更。


封闭trait(sealed trait)

刚才我的用词都是“大多数”、“通常情况下”,这是因为Rust有封闭trait(sealed trait)

它的特点是它只能被其它crate使用,而不能在其它crate中被实现。它可以防止trait添加新方法时造成破坏性的变更

封闭trait(sealed trait)并不是内建的功能,有几种实现方式。

封闭trait(sealed trait)常用于派生trait。具体来讲就是为实现特定其它trait的类型提供blanket implementation的trait。

看例子:

mod sealed { pub trait Sealed {} // 私有 trait,不对外公开 } // 只有 `i32` 和 `f64` 类型可以实现 `MyTrait` impl sealed::Sealed for i32 {} impl sealed::Sealed for f64 {} pub trait MyTrait: sealed::Sealed { fn describe(&self) -> String; } // Blanket Implementation:只有 `Sealed` 的实现者才可以使用 `MyTrait` impl MyTrait for i32 { fn describe(&self) -> String { format!("I am an i32: {}", self) } } impl MyTrait for f64 { fn describe(&self) -> String { format!("I am an f64: {}", self) } } // 测试 fn main() { let x: i32 = 42; let y: f64 = 3.14; println!("{}", x.describe()); // 输出: I am an i32: 42 println!("{}", y.describe()); // 输出: I am an f64: 3.14 }
  • Sealed私有的(因为它在 mod sealed 内),别的crate就不可能用的了它,以此实现了封闭的目的。
  • 只有i32f64被允许实现Sealed

上面的是一个比较简单的例子,我们接下来融入派生trait:

使用Sealed作为封闭 trait,限制BaseTrait只能被特定类型实现。
派生DerivedTrait,让它继承BaseTrait,并提供额外的行为。

mod sealed { pub trait Sealed {} // 私有 trait,不对外公开 } // 只有 `i32` 和 `f64` 可以实现 `BaseTrait` impl sealed::Sealed for i32 {} impl sealed::Sealed for f64 {} /// 基础 trait,只能被 `sealed::Sealed` 的类型实现 pub trait BaseTrait: sealed::Sealed { fn base_method(&self) -> String; } // Blanket implementation for BaseTrait impl BaseTrait for i32 { fn base_method(&self) -> String { format!("I am an i32: {}", self) } } impl BaseTrait for f64 { fn base_method(&self) -> String { format!("I am an f64: {}", self) } } /// 派生 trait,扩展 `BaseTrait` pub trait DerivedTrait: BaseTrait { fn derived_method(&self) -> String; } // Blanket implementation for DerivedTrait impl DerivedTrait for i32 { fn derived_method(&self) -> String { format!("Derived trait: {} squared = {}", self, self * self) } } impl DerivedTrait for f64 { fn derived_method(&self) -> String { format!("Derived trait: sqrt({}) = {}", self, self.sqrt()) } } fn main() { let x: i32 = 5; let y: f64 = 9.0; println!("{}", x.base_method()); // "I am an i32: 5" println!("{}", x.derived_method()); // "Derived trait: 5 squared = 25" println!("{}", y.base_method()); // "I am an f64: 9" println!("{}", y.derived_method()); // "Derived trait: sqrt(9) = 3" }
  • BaseTrait不能被外部类型实现,只能用于i32f64,因为它继承了sealed::Sealed
  • DerivedTrait扩展了BaseTrait,并增加了derived_method()
  • BaseTraitDerivedTrait只对i32f64提供实现,外部类型无法实现这些 trait

那么什么时候使用封闭trait呢?只有在外部crate不该实现你的trait时。这种形式会严重限制trait的可用性——下游trait无法为其自己类型实现该trait。

我们可以使用密封trait来限制可用作类型参数的类型。还记得我们之前写的Rocket结构体吗?(在 2.9.3. 类型系统 中)Rocket结构体的Stage泛型参数限制为了仅允许GroundedLaunched类型就使用这种方式。

2.11.2. 隐藏的契约

有时,你对代码的某一部分所做的更改会以微妙的方式影响到接口其他地方的契约。

这种情况主要发生在:
- 重新导出(re-exports)
- 自动trait(auto-trait)

重新导出(re-exports)

重新导出(re-exports)的操作在 【Rust自学】14.3.1. 使用pub use导出方便使用的API 中有过介绍。

如果你的接口的某部分暴露了外部类型,那么外部类型的任何更改也将成为你接口的变更。

最好用newtype(详见 【Rust自学】19.2.6. 使用newtype模式在外部类型上实现外部trait)包裹外部类型,仅仅暴露外部类型中你认为有用的部分。

自动trait(auto-trait)

有些trait根据类型的内容,会对其进行实现,比如说SendSync。而根据这些trait的特性,它们为接口中几乎每种类型都添加一个隐藏的承诺。

这些特性会传播,无论是具体的类型,还是impl Trait等类型擦除的情况。

这些trait的实现通常是编译器自动添加的,如果情况不适用,则不会自动添加。

举个例子:
- 类型A包含私有类型B,默认A和B都实现了Send
- 后来修改了B,让B不再实现Send,那么A也就实现不了Send
- 这样的情况就是破坏性的变化,而且这类变化还非常难以追踪和发现

针对这种问题,你可以在你的库里面包含一些简单的测试来检查你所有的类型是否实现了相关的trait。

举个代码例:

这是原本的代码:

use std::thread; /// 1. 私有类型 B,最初实现了 `Send` struct B; /// 2. 公开类型 A,包含 B struct A { _b: B, // 依赖 `B` 的特性 } // 3. 证明 `A` 实现了 `Send` fn assert_send<T: Send>() {} fn main() { assert_send::<A>(); // 通过,A 是 Send 的 // 4. 证明 A 可以在线程间安全传递 let a = A { _b: B }; thread::spawn(move || { let _ = a; // 运行成功,因为 A 仍然是 Send }).join().unwrap(); }

后面我们进行修改,使B不再实现Sendtrait:

use std::rc::Rc; /// 1. 修改 `B`,让它不再实现 `Send` /// `Rc<T>` 不是 `Send`,所以 B 也不是 `Send` struct B { _data: Rc<i32>, } /// 2. A 仍然包含 B struct A { _b: B, } // 3. 证明 `A` 实现了 `Send` fn assert_send<T: Send>() {} fn main() { assert_send::<A>(); // 编译错误[E0277]:`Rc<i32>` cannot be sent between threads safely(因此 `A: Send` 失败) let a = A { _b: B { _data: Rc::new(42) } }; thread::spawn(move || { let _ = a; // 这里会报错,因为 Rc<i32> 不能在线程间安全传递 }).join().unwrap(); }
  • B现在包含Rc<T>,但Rc<T>不是Send。这意味着B也不能Send,因为Rc<T>不能安全在线程间传递。
  • A也就不再Send,导致assert_send::<A>()编译错误。我们就可以在编译时发现错误。
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/24 22:42:56

131号文件落地,医疗设备行业告别“躺赚”时代

2026年7月14日&#xff0c;一份文件让整个医疗设备行业彻底失眠。国家卫健委、中医药管理局、疾控局三部门联合印发的《关于进一步做好公立医疗卫生机构医用设备集中采购工作的通知》&#xff08;国卫财务函〔2026〕131号&#xff09;&#xff0c;业内称之为“医疗设备集采总纲…

作者头像 李华
网站建设 2026/7/24 22:42:05

如何一键切换游戏DLSS版本?DLSS Swapper深度揭秘与实战指南

如何一键切换游戏DLSS版本&#xff1f;DLSS Swapper深度揭秘与实战指南 【免费下载链接】dlss-swapper 项目地址: https://gitcode.com/GitHub_Trending/dl/dlss-swapper DLSS Swapper是一款专为游戏玩家和开发者设计的开源工具&#xff0c;它解决了多平台游戏DLSS版本…

作者头像 李华
网站建设 2026/7/24 22:42:03

AMD Ryzen SMUDebugTool:硬件级调试与性能调优完全指南

AMD Ryzen SMUDebugTool&#xff1a;硬件级调试与性能调优完全指南 【免费下载链接】SMUDebugTool A dedicated tool to help write/read various parameters of Ryzen-based systems, such as manual overclock, SMU, PCI, CPUID, MSR and Power Table. 项目地址: https://g…

作者头像 李华
网站建设 2026/7/24 22:35:21

完全指南:5分钟掌握免费在线EPUB编辑器EPubBuilder

完全指南&#xff1a;5分钟掌握免费在线EPUB编辑器EPubBuilder 【免费下载链接】EPubBuilder 一款在线的epub格式书籍编辑器 项目地址: https://gitcode.com/gh_mirrors/ep/EPubBuilder 你是否曾为制作专业电子书而烦恼&#xff1f;下载复杂的软件、学习繁琐的操作流程、…

作者头像 李华