# Rust - 介面設計建議之不意外(unsurprising) 書:Rust for Rustaceans ## Rust介面設計的原則(建議) - 四個原則: - 不意外(unsurprising) - 靈活(flexible) - 顯而易見(obvious) - 受約束(constraine ...
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 有:
給類型實現 Default
trait 和空的 new
構造函數是常見和有必要的。
new
是 Rust 中常規的構造函數,所以不使用參數來構造基本的類型時, new
對使用者來說就理應存在。
default
方法功能上與 new
方法一致,所以也應當存在。
建議實現 Debug Trait
- 幾乎所有的類型都能、應該實現 Debug
#[derive(Debug)]
,通常是最佳實現方式- 註意:派生的 Trait 會為任意泛型參數添加相同的約束(bound)
- 利用
fmt::Formatter
提供的各種 debug_xxx 輔助方法手動實現debug_struct
debug_tuple
debug_list
debug_set
debug_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: Trait
Box<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