# Rust語言 - 介面設計的建議之受約束(Constrained) - [Rust API 指南 GitHub](https://github.com/rust-lang/api-guidelines):https://github.com/rust-lang/api-guidelines - ...
Rust語言 - 介面設計的建議之受約束(Constrained)
- Rust API 指南 GitHub:https://github.com/rust-lang/api-guidelines
- Rust API 指南 中文:https://rust-chinese-translation.github.io/api-guidelines/
- Rust API 指南:https://rust-lang.github.io/api-guidelines/
受約束(Constrained)
介面的更改要三思
- 做出用戶可見的更改,需三思而後行
- 確保你做出的變化:
- 不會破壞現有用戶的代碼
- 這次變化應保留一段時間
- 頻繁的向後不相容的更改(主版本增加),會引起用戶不滿
- 確保你做出的變化:
向後不相容的修改
- 有些是顯而易見的,有些則很微妙(與 Rust 工作方式相關)
- 主要介紹微妙棘手的更改,以及如何為其制定計劃
- 有時需要在介面靈活性上做出權衡、妥協
類型修改
- 移除或重命名公共類型幾乎肯定會破壞用戶的代碼
- 解決:儘可能利用可見性修飾符
- 例如 pub(crate)、pub(in path) ...
- 公共類型越少,更改時(保證不會破壞現有代碼)就越自由
- 解決:儘可能利用可見性修飾符
例子一:
pub mod outer_mod {
pub mod inner_mod {
// This function is visible within `outer_mod`
pub(in crate::outer_mod) fn outer_mod_visible_fn() {}
// This function is visible to the entire crate
pub(crate) fn crate_visible_fn() {}
// This function is visible within `outer_mod`
pub(super) fn super_mod_visible_fn() {
// This function is visible since we're in the same `mod`
inner_mod_visible_fn();
}
// This function is visible only within `inner_mod`,
// which is the same as leaving it private.
pub(self) fn inner_mod_visible_fn() {}
}
pub fn foo() {
inner_mod::outer_mod_visible_fn();
inner_mod::crate_visible_fn();
inner_mod::super_mod_visible_fn();
// This function is no longer visible since we're outside of `inner_mod`
// Error! `inner_mod_visible_fn` is private
// inner_mod::inner_mod_visible_fn();
}
}
fn bar() {
// This function is still visible since we're in the same crate
outer_mod::inner_mod::crate_visible_fn();
// This function is no longer visible since we're outside of `outer_mod`
// Error! `super_mod_visible_fn` is private
outer_mod::inner_mod::super_mod_visible_fn();
// This function is no longer visible since we're outside of `outer_mod`
// Error! `outer_mod_visible_fn` is private
outer_mod::inner_mod::outer_mod_visible_fn();
outer_mod::foo();
}
fn main() {
bar()
}
- 用戶代碼不僅僅通過名稱依賴於你的類型
例子二:
lib.rs
pub struct Unit;
main.rs
fn main() {
let u = constrained::Unit; // v0 庫是 constrained
}
修改一
lib.rs
pub struct Unit {
pub field: bool,
}
main.rs
fn is_true(u: constrained::Unit) -> bool {
matches!(u, constrained::Unit { field: true })
}
fn main() {
let u = constrained::Unit; // v0 報錯,因為添加欄位之後 Unit struct 原有的構造方式不可用
}
修改二
lib.rs
pub struct Unit {
local: i32, // 增加私有欄位
}
main.rs
fn main() {
let u = constrained::Unit; // v0 報錯,雖然欄位看不見,但是編譯器可以看到
}
- Rust 提供
#[non_exhaustive]
來緩解這些問題non_exhaustive
表示類型或枚舉在將來可能會添加更多欄位或變體- 它可以應用於 struct、enums 和 enum variants。
- 在其它 crate,使用
non_exhaustive
定義的類型,編譯器會禁止:- 隱式構造,
lib::Unit { field1: true }
- 以及非窮盡模式匹配(即沒有尾隨 , .. 的模式)
- 隱式構造,
- 若介面穩定的話,儘量避免使用該註解
例子三:
lib.rs
#[non_exhaustive]
pub struct Config {
pub window_width: u16,
pub window_height: u16,
}
fn SomeFunction() {
let config = Config {
window_width: 640,
window_height: 480,
};
// Non-exhaustive structs can be matched on exhaustively within the defining crate.
if let Config {
window_width,
window_height,
} = config
{
// ...
}
}
main.rs
use constrained::Config;
fn main() {
// Not allowed.
let config = Config { // 報錯
window_width: 640,
window_height: 480,
};
if let Config {
window_width,
window_height,
.. // This is the only difference. 必須加 .. 否則報錯
} = config
{
// ...
}
}
Trait 實現
- 一致性規則禁止把某個 Trait 為某類型進行多重實現
- 破壞性變更
- 為現有 Trait 添加 Blanket Implementation 通常是破壞性變更(
impl <T> Foo for T
) - 為現有類型實現外部 Trait,或為外部類型實現現有 Trait
- 移除 Trait 實現
- 為新類型實現 Trait 就不是問題
- 為現有 Trait 添加 Blanket Implementation 通常是破壞性變更(
- 為現有類型實現任何 Trait 都要小心
例子四:
lib.rs
pub struct Unit;
pub trait Fool {
fn foo(&self);
}
main.rs
use constrained::{Foo1, Unit};
trait Foo2 {
fn foo(&self);
}
impl Foo2 for Unit {
fn foo(&self) {
println!("foo2");
}
}
fn main() {
Unit.foo()
}
修改一
lib.rs
pub struct Unit;
pub trait Fool {
fn foo(&self);
}
// case 1: Add impl Foo1 for Unit in this crate
impl Foo1 for Unit {
fn foo(&self) {
println!("foo1");
}
}
main.rs
use constrained::{Foo1, Unit};
trait Foo2 {
fn foo(&self);
}
impl Foo2 for Unit {
fn foo(&self) {
println!("foo2");
}
}
fn main() {
Unit.foo() // 報錯
}
修改二
lib.rs
pub struct Unit;
pub trait Fool {
fn foo(&self);
}
// case 2: Add a new public Trait
pub trait Bar1 {
fn foo(&self); // with the same name
}
impl Bar1 for Unit {
fn foo(&self) {
println!("bar1");
}
}
main.rs
use constrained::{Foo1, Unit};
trait Foo2 {
fn foo(&self);
}
impl Foo2 for Unit {
fn foo(&self) {
println!("foo2");
}
}
fn main() {
Unit.foo() // 因為沒有引入lib.rs中的Bar1,所以暫時沒有報錯
}
main.rs
use constrained::*;
trait Foo2 {
fn foo(&self);
}
impl Foo2 for Unit {
fn foo(&self) {
println!("foo2");
}
}
fn main() {
Unit.foo() // 報錯
}
- 大多數到現有 Trait 的更改也是破壞性更改
- 改變方法簽名
- 添加新方法
- 如果有預設實現倒是可以
- 封閉 Trait(Sealed Trait):
- 只能被其它 crate 用,不能實現
- 防止 Trait 添加新方法時造成破壞性變更
- 不是內建功能,有多種實現方法
- Sealed Trait 常用於派生 Trait
- 為實現特定其它 Trait 的類型提供 blanket implementation 的 Trait
- 封閉 Trait(Sealed Trait):
- 只有在外部 crate 不該實現你的 Trait 時,才使用 Sealed Trait
- 嚴重限制 Trait 的可用性
- 下游 crate 無法為其自己類型實現該 Trait
- 可使用 Sealed Trait 來限制可用作類型參數的類型
- 例:將 Rocket 示例中的 Stage 類型限製為僅允許 Grounded 和 Launched 類型
例子五:
lib.rs
use std::fmt::{Debug, Display};
mod sealed {
use std::fmt::{Debug, Display};
pug trait Sealed {}
impl<T> Sealed for T where T: Debug + Display {}
}
pub trait CanUseCannotImplement: sealed::Sealed {
// ..
}
impl<T> CanUseCannotImplement for T where T: Debug + Display {}
main.rs
use std::fmt::{Debug, Display};
use constrained::CanUseCannotImplement;
pub struct Bar {}
impl Debug for Bar {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
Ok(())
}
}
impl Display for Bar {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
Ok(())
}
}
// impl CanUseCannotImplement for Bar {} // 報錯 因為在 lib.rs 中已經實現好了,不能再實現
// Conflicting implementation,
// The trait `CanUseCannotImplement` has been already implemented
// for the types that satisfy the bounds specified by the sealed trait which are `Debug + Display`
pub struct Foo {}
impl CanUseCannotImplement for Foo {} // 報錯 沒有實現 Debug 和 Display
fn main() {}
隱藏的契約
- 有時,你對代碼的某一部分所做的更改會以微妙的方式影響到介面其他地方的契約。這種情況主要發生在:
- 重新導出(re-exports)
- 自動 Traits (auto-traits)
隱藏的契約 - 重新導出(Re-Exports)
- 如果你的介面的某部分暴露了外部類型,那麼外部類型的任何更改也將成為你介面的變更
- 最好用新類型模式(newtype pattern)包裹外部類型,僅僅暴露外部類型中你認為有用的部分
例子六:
lib.rs
// 你的 crate,叫 bestiter
pub fn iter<T>() -> itercrate::Empty<T> { .. }
// 依賴的外部 crate,叫 itercrate (v1.0),提供了 Empty<T> 類型
// 用戶的 crate 中
struct EmptyIterator { it: itercrate::Empty<()> }
EmptyIterator { it: bestiter::iter() }
// ---------------------------------------------------------------
// 你的 crate, 叫 bestiter
pub fn iter<T>() -> itercrate::Empty<T> { .. }
// 依賴的外部crate,叫 itercrate,提供了 Empty<T> 類型
// 依賴的版本改為 v2.0,別處沒有更改
// 編譯器認為:itercrate1.0::Empty 和 itercrate2.0::Empty 是不同的類型
// 導致破壞性變更
// 用戶的 crate 中
struct EmptyIterator { it: itercrate::Empty<()> }
隱藏的契約 - 自動 Trait(Auto-Traits)
- 有些 Trait 根據類型的內容,會對其進行實現
- 根據它們的特性,它們為介面中幾乎每種類型都添加一個隱藏的承諾
- Send、Sync
- Unpin、Sized、UnwindSafe 也存在類似問題
- 這些特性會傳播,無論是具體類型,還是 impl Trait 等類型擦除情況
- 這些 Trait 的實現通常是編譯器自動添加的
- 如果情況不適用,則不會自動添加
- 例如:
- 類型 A 包含私有類型 B,預設 A 和 B 都是 Send 的
- 如果修改 B,讓 B 不再是 Send 的,那麼 A 也變成不 Send 的了
- 破壞性變化
- 這類變化難以追蹤和發現
- 包含一些簡單的測試,檢查你所有的類型都實現了相關的 Traits
例子七:
fn is_normal<T: Sized + Send + Sync + Unpin>() {}
#[test]
fn normal_types() {
is_normal::<MyType>();
}
設計 Rust 介面的總結
- 不讓人感到意外、靈活的、顯而易見的和受限制的
本文來自博客園,作者:尋月隱君,轉載請註明原文鏈接:https://www.cnblogs.com/QiaoPengjun/p/17495517.html