喜歡的話別忘了點贊、收藏加關注哦(加關注即可閱讀全文),對接下來的教程有興趣的可以關注專欄。謝謝喵!(=・ω・=)
2.10.1. 接口的更改要三思
如果你的接口要做出對用戶可見的更改,那么一定要三思而后行。
你需要確保你做出的變化:
- 不會破壞現有用戶的代碼
- 這次變化應該保留一段時間
頻繁推送向后不兼容的更改(主版本增加),會導致用戶的不滿。
2.10.2. 向后不兼容的更改
有些向后不兼容的更改是顯而易見的,比如說你改變公共類型的名稱,或事為它添加一個新的公共方法。
有些向后不兼容的更改則很微妙,這與Rust的工作方式息息相關。這篇文章主要講的就是這種更改,以及你作為開發者應該如何為其制定修改計劃。
在這個過程中,有時候你就需要在接口的靈活性上做出權衡與妥協。
2.10.3. 對類型進行修改
如果你移除或重命名一個公共類型幾乎肯定會破壞用戶的代碼,解決辦法就是盡可能利用可見性修飾符。比如說:
pub(crate)
:對當前這個crate可見pub(in path)
:對指定的路徑可見
看個例子:
pub mod outer_mod {pub mod inner_mod {// 該函數僅對 `outer_mod` 可見pub(in crate::outer_mod) fn outer_mod_visible_fn() {}// 該函數對整個 crate 可見pub(crate) fn crate_visible_fn() {}// 該函數僅對 `outer_mod` 可見(使用 `super` 指向外部模塊)pub(super) fn super_mod_visible_fn() {// 由于 `inner_mod_visible_fn` 在相同模塊內可見,可以正常調用inner_mod_visible_fn();}// 該函數僅對 `inner_mod` 內部可見,相當于 `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();// 該函數不再可見,因為我們已經在 `inner_mod` 之外// Error! `inner_mod_visible_fn` 是私有的inner_mod::inner_mod_visible_fn();}
}fn bar() {// 這個函數仍然可見,因為我們在同一個 crate 內outer_mod::inner_mod::crate_visible_fn();// 這個函數在 `outer_mod` 之外不再可見// Error! `super_mod_visible_fn` 是私有的outer_mod::inner_mod::super_mod_visible_fn();// 這個函數在 `outer_mod` 之外也不可見// Error! `outer_mod_visible_fn` 是私有的outer_mod::inner_mod::outer_mod_visible_fn();outer_mod::foo();
}
inner_mod
模塊中函數的可見性控制:
outer_mod_visible_fn()
: 僅在outer_mod
內部可見,外部無法訪問。crate_visible_fn()
: 整個crate
可見,即bar()
仍然可以訪問它。super_mod_visible_fn()
: 僅outer_mod
內部可見,bar()
無法訪問。inner_mod_visible_fn()
: 私有,僅inner_mod
內部可見。
你寫的API中公共類型越少,更改時就越自由(自由指保證不會破壞現有代碼)。
#[non_exhaustive]
注解
用戶的代碼不僅僅通過名稱依賴于你的類型。看個例子:
一個破壞性變更的例子
最開始在lib.rs中我寫了一個結構體名叫Unit
:
pub struct Unit;
然后我在main.rs中使用了Unit
:
fn main() {let u = constrained::Unit;
}
- 這沒有任何問題。
后來呢,我對Unit
進行了一些修改,因為用戶要用:
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;
}
is_true
這個函數用到了修改后Unit
的字段- 但是
main
函數中本來的代碼就會報錯
這種情況也會在Unit
的field
是私有字段時發生。因為編譯器知道Unit
有字段,而你沒有填寫這個字段的值。
解決方案
針對這種情況,Rust提供了#[non_exhaustive]
注解來緩解這些問題。它可以引用于struct
、enum
和enum
的變體。這個注解表示類型或枚舉在將來可能會添加更多字段或變體。
如果你使用了它,那么別人在使用你的crate時,編譯器會:
- 禁止顯式的構造,比如:
lib::Unit { field: true }
- 禁止非窮盡模式的匹配(即沒有尾隨
..
的模式)
如果你的接口比較穩定,就應該避免使用這個注解。
看例子:
lib.rs:
#[non_exhaustive]
pub struct Config {pub window_width: u16,pub window_height: u16,
}fn SomeFunction() {let config: 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: u16,window_height: u16,} = config{// ...}
}
- 標注了
#[non_exhaustive]
,lib.rs里仍然可以使用顯式的構造,仍然可以使用非窮盡模式的匹配,因為這些代碼與定義這個結構體的代碼屬于同一crate之內
那么我在main.rs這么寫呢:
use constrained::Config;fn main() {let config: Config = Config {window_width: 640,window_height: 480,};if let Config {window_width: u16,window_height: u16,} = config
}
- 這樣寫就會報錯,因為這里的代碼屬于外部crate,編譯器就會靜止上面所說的兩種操作
我們可以稍微改一下代碼使main.rs中的非窮盡模式的匹配變成窮盡模式的匹配:
if let Config {window_width: u16,window_height: u16,.. // 它用于忽略結構體、元組或枚舉中的其余字段或變體
} = config