Rust - 介面設計建議之不意外(unsurprising)
書:Rust for Rustaceans
Rust介面設計的原則(建議)
- 四個原則:
- 不意外(unsurprising)
- 靈活(flexible)
- 顯而易見(obvious)
- 受約束(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/
不意外(unsurprising)
- 最少意外原則:
- 介面應盡可能直觀(可預測,用戶能猜對)
- 至少應該不讓人感到驚奇
- 核心思想:
- 貼近用戶已經知道的東西(不必重學概念)
- 讓介面可預測:
- 命名
- 實作常用的 Traits
- 人體工程學(Ergonomic)Traits
- 包裝型別(Wrapper Type)
命名實踐
- 介面的名稱,應符合慣例,便于推斷其功能
- 例:
- 方法 iter,大概率應將 &self 作為引數,并應該回傳一個迭代器(iterator)
- 叫做 into_inner 的方法,大概率應將 self 作為引數,并回傳某個包裝的型別
- 叫做 SomethingError 的型別,應實作 std::error::Error,并出現在各類 Result 里
- 例:
- 將通用/常用的名稱依然用于相同的目的,讓用戶好猜、好理解
- 推論:同名的事物應該以相同的方式作業
- 否則,用戶大概率會寫出錯誤的代碼
- 遵循
as_,to_,into_規范 用以特定型別轉換
| 名稱前綴 | 記憶體代價 | 所有權 |
|---|---|---|
as_ |
無代價 | borrowed -> borrowed |
to_ |
代價昂貴 | borrowed -> borrowed borrowed -> owned (非 Copy 型別) owned -> owned (Copy 型別) |
into_ |
視情況而定 | owned -> owned (非 Copy 型別) |
實作常用的 Trait
- 用戶通常會假設介面中的一切均可“正常作業”,例:
- 使用
{:?}列印任何型別 - 可發送任何東西到另外的執行緒
- 期望每個型別都是 Clone 的
- 使用
- 建議積極實作大部分標準 Trait,即使不立即用到
- 用戶無法為外部型別實作外部的 Trait
- 即使能包裝你的介面型別,也難以寫出合理實作
Rust 的 trait 系統堅持 孤兒原則 :大致說的是, 每個 impl 塊必須
- 要么存在于定義 trait 的 crate 中,
- 要么存在于給型別實作 trait 的 crate 中,
所以,定義新型別的 crates 應該盡早實作所有合適的、常見的 traits ,
std 中可給型別實作的、最重要的、常見的 traits 有:
CopyCloneEqPartialEqOrdPartialOrdHashDebugDisplayDefault
給型別實作 Default trait 和空的 new 建構式是常見和有必要的,
new 是 Rust 中常規的建構式,所以不使用引數來構造基本的型別時, new 對使用者來說就理應存在,
default 方法功能上與 new 方法一致,所以也應當存在,
建議實作 Debug Trait
- 幾乎所有的型別都能、應該實作 Debug
#[derive(Debug)],通常是最佳實作方式- 注意:派生的 Trait 會為任意泛型引數添加相同的約束(bound)
- 利用
fmt::Formatter提供的各種 debug_xxx 輔助方法手動實作debug_structdebug_tupledebug_listdebug_setdebug_map
例子一
use std::fmt::Debug;
#[derive(Debug)]
struct Pair<T> {
a: T,
b: T,
}
fn main() {
let pair = Pair {a: 5, b: 10};
println!("Pair: {:?}", pair); // i32 實作了 Debug Trait 故可以列印出來
}
例子二
use std::fmt::Debug;
struct Person {
name: String,
}
#[derive(Debug)]
struct Pair<T> {
a: T,
b: T,
}
fn main() {
let pair = Pair {
a: Person { name: "Dave".to_string() },
b: Person { name: "Nick".to_string() },
};
println!("Pair: {:?}", pair); // 報錯 `Person` doesn't implement `Debug` Person 沒有實作 Debug Trait
}
例子三
use std::fmt;
struct Pair<T> {
a: T,
b: T,
}
impl<T: fmt::Debug> fmt::Debug for Pair<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Pair").field("a", &self.a).field("b", &self.b).finish()
}
}
fn main() {
let pair = Pair { a: 5, b: 10 };
println!("Pair: {:?}", pair);
}
建議實作 Send 和 Sync(unpin)
- 不是 Send 的型別無法放在 Mutex 中,也不能在包含執行緒池的應用程式中傳遞使用
例子四
#[derive(Debug)]
struct MyBox(*mut u8);
unsafe impl Send for MyBox {}
use std::rc::Rc;
fn main() {
let mb = MyBox(Box::into_raw(Box::new(42)));
let x = Rc::new(42);
std::thread::spawn(move || {
println!("{:?}", x); // error: `Rc<i32>` cannot be sent between threads safely
});
//std::thread::spawn(move || {
// println!("{:?}", mb); // mb 實作了 Send Trait
//});
}
- 不是 Sync 的型別無法通過 Arc 共享,也無法被放置在靜態變數中
例子五
use std::cell::RefCell;
use std::sync::Arc;
fn main() {
let x = Arc::new(RefCell::new(42));
std::thread::spawn(move || {
let mut x = x.borrow_mut(); // error: `RefCell<i32>` cannot be shared between threads safely
*x += 1;
});
}
- 如果沒實作上述 Trait,建議在檔案中說明
建議實作 Clone 和 Default
例子六
#[derive(Debug, Clone)]
struct Person {
name: String,
age: u32,
}
impl Person {
fn new(name: String, age: u32) -> Person {
Person { name, age }
}
}
fn main() {
let person1 = Person::new("Alice".to_owned(), 25);
let person2 = person1.clone();
println!("Person 1: {:?}", person1);
println!("Person 2: {:?}", person2);
}
例子七
#[derive(Default)]
struct Point {
x: i32,
y: i32,
}
fn main() {
let point = Point::default(); // 提供默認的初始值
println!("Point: ({}, {})", point.x, point.y); // Point: (0, 0)
}
- 如果沒實作上述 Trait,建議在檔案中說明
建議實作 PartialEq、PartialOrd、Hash、Eq、Ord
- PartialEq 特別有用
- 用戶會希望使用 == 或 assert_eq! 比較你型別的兩個實體
例子八
#[derive(Debug, PartialEq)]
struct Point {
x: i32,
y: i32,
}
fn main() {
let point1 = Point { x: 1, y: 2 };
let point2 = Point { x: 1, y: 2 };
let point3 = Point { x: 3, y: 4 };
println!("point1 == point2: {}", point1 == point2);
println!("point1 == point3: {}", point1 == point3);
}
- PartialOrd 和 Hash 相對更專門化
- 將型別作為 Map 中的 Key
- 須實作 PartialOrd,以便進行 Key 的比較
- 使用
std::collection的集合型別進行去重的型別- 須實作 Hash,以便進行哈希計算
- 將型別作為 Map 中的 Key
例子九
use std::collections::BTreeMap;
#[derive(Debug, PartialEq, PartialOrd, Eq, Ord, Clone)]
struct Person {
name: String,
age: u32,
}
fn main() {
let mut ages = BTreeMap::new();
let person1 = Person {
name: "Alice".to_owned(),
age: 25,
};
let person2 = Person {
name: "Bob".to_owned(),
age: 30,
};
let person3 = Person {
name: "Charlie".to_owned(),
age: 20,
};
ages.insert(person1.clone(), "Alice's age");
ages.insert(person2.clone(), "Bob's age");
ages.insert(person3.clone(), "Charlie's age");
for (person, description) in &ages {
println!("{}: {} - {:?}", person.name, person.age, description);
}
}
例子十
use std::collections::HashSet;
use std::hash::{Hash, Hasher};
#[derive(Debug, PartialEq, Eq, Clone)]
struct Person {
name: String,
age: u32,
}
impl Hash for Person {
fn hash<H: Hasher>(&self, state: &mut H) {
self.name.hash(state);
self.age.hash(state);
}
}
fn main() {
let mut persons = HashSet::new();
let person1 = Person {
name: "Alice".to_owned(),
age: 25,
};
let person2 = Person {
name: "Bob".to_owned(),
age: 30,
};
let person3 = Person {
name: "Charlie".to_owned(),
age: 20,
};
persons.insert(person1.clone());
persons.insert(person2.clone());
persons.insert(person3.clone());
println!("Persons: {:?}", persons);
}
- Eq 和 Ord 有額外的語意要求(相對 PartialEq 和 PartialOrd)
- 只應在確信這些語意適用于你的型別時才實作它們
例子十一
// Eq
// 反身性(Reflexivity):對于任何物件 x,x == x 必須為真,
// 對稱性(Symmetry):對于任何物件 x 和 y,如果 x == y 為真,則 y == x 也必須為真,
// 傳遞性(Transitivity):對于任何物件 x、y 和 z,如果 x == y 為真,并且 y == z 為真,則 x == z 也必須為真,
// Ord
// 自反性(Reflexivity):對于任何物件 x,x <= x 和 x >= x 必須為真,
// 反對稱性(Antisymmetry):對于任何物件 x 和 y,如果 x <= y 和 y <= x 都為真,則 x == y 必須為真,
// 傳遞性(Transitivity):對于任何物件 x、y 和 z,如果 x <= y 和 y <= z 都為真,則 x <= z 必須為真,
fn main() {
}
建議實作 serde 下的 Serialize、Deserialize
serde_derive(crate)提供了機制,可以覆寫單個欄位或列舉變體的序列化- 由于 serde 是第三方庫,你可能不希望強制添加對它的依賴
- 大多數庫選擇提供一個 serde 的功能(feature),只有當用戶選擇啟用該功能時才添加對 serde 的支持
例子十二:你寫的庫
[dependencies]
serde = { version = "1.0", optional = true}
[features]
serde = ["serde"]
例子十三:別人用的時候
[dependencies]
mylib = { version = "0.1", features = ["serde"] }
為什么沒建議實作 Copy
- 用戶通常不期望型別是 Copy 的
- 如果想要兩個副本,通常希望呼叫 clone
- Copy 改變了移動給定型別值的語意
- 讓用戶 surprise
- Copy 型別受到很多限制,一個最初簡單的型別很容易變得不再滿足 Copy 的要求
- 例如持有了 String 或者其他非 Copy 的型別 ---> 不得不移除 Copy
例子十四
#[derive(Debug, Copy, Clone)]
struct Point {
x: i32,
y: i32,
}
fn main() {
let point1 = Point { x: 10, y: 20 };
let point2 = point1; // 這里發生復制,而不是移動
println!("point1: {:?}", point1);
println!("point2: {:?}", point2);
}
人體工程學 Trait 實作
- Rust 不會自動為實作 Trait 的型別的參考提供對應的實作
- Bar 實作了 Trait,也不能將 &Bar 傳遞給
fn foo<T: Trait>(t: T)- 因為 Trait 可能包含接受 &mut self 或 self 的方法,而這些方法無法在 &Bar 上呼叫
- 對于看到 Trait 只有 &self 方法的用戶來說,這會非常令人驚訝
- Bar 實作了 Trait,也不能將 &Bar 傳遞給
- 定義新的 Trait 時,通常需要為下列提供相應的全域實作
&T where T: Trait&mut T where T: TraitBox<T> where T: Trait
- Iterator(迭代器):為型別的參考添加 Trait 實作
- 對于任何可迭代的型別,考慮為 &MyType 和 &mut MyType 實作 IntoIterator
- 在回圈中可直接使用借用實體,符號用戶預期,
- 對于任何可迭代的型別,考慮為 &MyType 和 &mut MyType 實作 IntoIterator
包裝型別(Wrapper Types)
- Rust 沒有傳統意義上的繼承
- Deref 和 AsRef 提供了類似繼承的東西
- 你有一個型別為 T 的值,并滿足
T: Deref<Target = U>,可以在 T 型別值上直接呼叫型別 U 的方法
- 你有一個型別為 T 的值,并滿足
例子十五
use std::ops::Deref;
struct MyVec(Vec<i32>);
impl Deref for MyVec {
type Target = Vec<i32>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
fn main() {
let my_vec = MyVec(vec![1, 2, 3, 4, 5]);
println!("Length: {}", my_vec.len());
println!("First element: {}", my_vec[0]);
}
- 如果你提供了相對透明的型別(例 Arc)
- 實作 Deref 允許你的包裝型別在使用點運算子時,自動解參考為內部型別,從而可以直接呼叫內部型別的方法
- 如果訪問內部型別不需要任何復雜或潛在的低效邏輯,應考慮實作 AsRef,這樣用戶可以輕松地將 &WrapperType 作為 &InnerType 使用
- 對于大多數包裝型別,還應該在可能的情況下實作
From<InnerType>和Into<InnerType>,以便用戶可輕松地添加或移除包裝,
例子十六
use std::ops::Deref;
struct Wrapper(String);
impl Deref for Wrapper {
type Target = String;
fn deref(&self) -> *Self::Target {
&self.0
}
}
impl AsRef<str> for Wrapper {
fn as_ref(&self) -> &str {
&self.0
}
}
impl From<String> for Wrapper {
fn from(s: String) -> Self {
Wrapper(s)
}
}
impl From<Wrapper> for String {
fn from(wrapper: Wrapper) -> Self {
wrapper.0
}
}
fn main() {
let wrapper = Wrapper::from("Hello".to_string());
// 使用 . 運算子呼叫內部字串型別的方法
println!("Length: {}", wrapper.len());
// 使用 as_ref 方法將 Wrapper 轉換為 &str 型別
let inner_ref: &str = wrapper.as_ref();
println!("Inner: {}", inner_ref);
// 將 Wrapper 轉換為內部型別 String
let inner_string: String = wrapper.into();
println!("Inner String: {}", inner_string);
}
- Borrow Trait (與 Deref 和 AsRef 有些類似)
- 針對更為狹窄的使用情況進行了定制:
- 允許呼叫者提供同一型別的多個本質上相同的變體中的任意一個
- 可叫做:Equivalent
- 例:對于一個
HashSet<String>,Borrow 允許呼叫者提供&str或&String,- 雖然使用 AsRef 也可以實作類似的效果,但如果沒有 Borrow 的額外要求,這種實作時不安全的,因為 Borrow 要求目標型別實作的 Hash、Eq、和 Ord 必須與實作型別完全相同
- Borrow 還為
Borrow<T>、&T和&mut T提供了通用實作- 這使得在 Trait 約束中使用它來接受給定型別的擁有值或參考值非常方便,
- 允許呼叫者提供同一型別的多個本質上相同的變體中的任意一個
- Borrow 僅適用于當你的型別本質上與另一個型別等價時
- 而 Deref 和 AsRef 則適用于更廣泛地實作你的型別可以“充當”的情況
- 針對更為狹窄的使用情況進行了定制:
例子十七
use std::borrow::Borrow;
fn print_length<S>(string: S)
where
S: Borrow<str>,
{
println!("Length: {}", string.borrow().len());
}
fn main() {
let str1: &str = "Hello";
let string1: String = String::from("World");
print_length(str1);
print_length(string1);
}
本文來自博客園,作者:尋月隱君,轉載請注明原文鏈接:https://www.cnblogs.com/QiaoPengjun/p/17467240.html
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/554671.html
標籤:其他
上一篇:Mybatis的parameterType造成執行緒阻塞問題分析
下一篇:返回列表
