国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

Rust - 接口設計建議之靈活(flexible)

這篇具有很好參考價值的文章主要介紹了Rust - 接口設計建議之靈活(flexible)。希望對大家有所幫助。如果存在錯誤或未考慮完全的地方,請大家不吝賜教,您也可以點擊"舉報違法"按鈕提交疑問。

Rust - 接口設計建議之靈活(flexible)

靈活(flexible)

代碼的契約(Contract)

  • 你寫的代碼包含契約
  • 契約:
    • 要求:代碼使用的限制
    • 承諾:代碼使用的保證
  • 設計接口時(經(jīng)驗法則):
    • 避免施加不必要的限制,只做能夠兌現(xiàn)的承諾
      • 增加限制 或 取消承諾:
        • 重大的語義版本更改
        • 可導致其他代碼出問題
      • 放寬限制 或 提供額外的承諾:
        • 通常是向后兼容的

限制(Restrictions)與承諾(Promises)

  • Rust中,限制的常見形式:
    • Trait 約束(Trait Bound)
    • 參數(shù)類型(Argument Types)
  • 承諾的常見形式:
    • Trait 的實現(xiàn)
    • 返回類型
  • fn frobnicate1(s: String) -> String
    • 契約:調用者進行內存分配,承諾返回擁有的 String -> 無法改為 “無需內存分配” 的函數(shù)
  • fn frobnicate2(s: &str) -> Cow<'_, str>
    • 放寬了契約:只接收字符串的引用,承諾返回字符串的引用或一個擁有的 String
  • fn frobnicate3(s: impl AsRef<str>) -> impl AsRef<str>
    • 進一步放寬契約:要求傳入能產生字符串引用的類型,承諾返回值可產生字符串引用

例子一

use std::borrow::Cow;

fn frobnicate3<T: AsRef<str>>(s: T) -> T {
  s
}

fn main() {
  let string = String::from("example");
  let borrowed: &str = "hello";
  let cow: Cow<str> = Cow::Borrowed("world");
  
  let result1: &str = frobnicate3::<&str>(string.as_ref());
  let result2: &str = frobnicate3::<&str>(borrowed);
  let result3 = frobnicate3(cow);
  
  println!("Result1: {:?}", result1);
  println!("Result2: {:?}", result2);
  println!("Result3: {:?}", result3);
}
  • 都傳入字符串,返回字符串,但契約不同
  • 沒有更好。要仔細規(guī)劃契約,否則改變契約會引起破壞

泛型參數(shù)(Generic Arguments)

  • 通過泛型放寬對函數(shù)的要求
    • 大多數(shù)情況下值得使用泛型代替具體類型

例子二

// 你有一個函數(shù),它接受一個實現(xiàn)了 AsRef<str> trait 的參數(shù)
fn print_as_str<T: AsRef<str>>(s: T) {
  println!("{}", s.as_ref());
}

// 這個函數(shù)是泛型的,它對 T 進行了泛型化,
// 這意味著它會對你使用它的每一種實現(xiàn)了 AsRef<str> 的類型進行單態(tài)化。
// 例如,如果你用一個 String 和一個 &str 來調用它,
// 你就會在你的二進制文件中有兩份函數(shù)的拷貝:
fn main() {
  let s = String::from("hello");
  let r = "world";
  print_as_str(s);  // 調用 print_as_str::<String>
  print_as_str(r);  // 調用 print_as_str::<&str>
}

例子三

// 為了避免這種重復,你可以把函數(shù)改成接受一個 &dyn AsRef<str>:
fn print_as_str(s: &dyn AsRef<str>) {
  println!("{}", s.as_ref());
}

// 這個函數(shù)不再是泛型的,它接受一個 trait 對象,
// 它可以是任何實現(xiàn)了 AsRef<str> 的類型
// 這意味著它會在運行時使用動態(tài)分發(fā)來調用 as_ref 方法,
// 并且你只會在你的二進制文件中有一份函數(shù)的拷貝:
fn main() {
  let s = String::from("hello");
  let r = "world";
  print_as_str(&s);  // 傳遞一個類型為 &dyn AsRef<str> 的 trait 對象
  print_as_str(&r);  // 傳遞一個類型為 &dyn AsRef<str> 的 trait 對象
}
  • 不要走極端
  • 經(jīng)驗法則:
    • 用戶合理、頻繁的使用其他類型代替你最初選定的類型,那么參數(shù)定義為泛型更合適
  • 問題:通過單態(tài)化(monomorphization),會為每個使用泛型代碼的類型組合生成泛型代碼的副本
    • 擔心:讓很多參數(shù)變成泛型 --> 二進制文件過大
  • 解決:動態(tài)分發(fā)(dynamic dispatch),以忽略不計的性能成本來緩解這個問題
    • 對于以引用方式獲取的參數(shù)(dyn Trait 不是 Sized 的,需要使用寬指針來使用它們),可以使用動態(tài)分發(fā)代替泛型參數(shù)

例子四

// 假設我們有一個名為 process 的泛型函數(shù),它接受一個類型參數(shù) T 并對其執(zhí)行某些操作:
fn process<T>(value: T) {
  // 處理 value 的代碼
  println!("處理 T");
}
// 上述函數(shù)使用靜態(tài)分發(fā),這意味著在編譯時將為每個具體類型 T 生成相應的實現(xiàn)。

// 現(xiàn)在,假設調用者想要提供動態(tài)分發(fā)的方式,允許在運行時選擇實現(xiàn)。
// 它們可以通過傳遞 Trait 對象作為參數(shù),
// 使用 dyn 關鍵字來實現(xiàn)。以下是一個例子:
trait Processable {
  fn process(&self);
}

struct TypeA;
impl Processable for TypeA {
  fn process(&self) {
    println!("處理 TypeA");
  }
}

struct TypeB;
impl Processable for TypeB {
  fn process(&self) {
    println!("處理 TypeB");
  }
}

fn process_trait_object(value: &dyn Processable) {
  value.process();
}

// 如果調用者想要使用動態(tài)分發(fā)并在運行時選擇實現(xiàn),
// 它們可以調用 process_trait_object 函數(shù),并傳遞 Trait 對象作為參數(shù)。
// 調用者可以根據(jù)需求選擇要提供的具體實現(xiàn):
fn main() {
  let a = TypeA;
  let b = TypeB;
  
  process_trait_object(&a);
  process_trait_object(&b);
  
  process(&a);
  process(&b);
  process(&a as &dyn Processable);
  process(&b as &dyn Processable);
  
}
  • 使用動態(tài)分發(fā)(dynamic dispatch):
    • 代碼不會對性能敏感:可以接受
    • 在高性能應用中:在頻繁調用的熱循環(huán)中使用動態(tài)分發(fā)可能會成為一個致命問題
  • 在撰寫本文時,只有在簡單的 Trait 約束時,才能使用動態(tài)分發(fā)
    • T: AsRef<str>impl AsRef<str>
  • 對于更復雜的約束,Rust 無法構造動態(tài)分發(fā)的虛函數(shù)表(vtable)
    • 因此無法使用類似 &dyn Hash + Eq 這樣的組合約束。
  • 使用泛型時,調用者始終可以通過傳遞一個 Trait 對象來選擇動態(tài)分發(fā)
  • 反過來不成立:如果你接受一個 Trait 對象作為參數(shù),那么調用者必須提供 Trait 對象,而無法選擇使用靜態(tài)分發(fā)
  • 從具體類型開始編寫接口,然后逐漸將它們轉換為泛型
    • 可行,但不一定是向下兼容

例子五

fn foo(v: &Vec<usize>) {
  // 處理 v 的代碼
  // ...
}

// 現(xiàn)在,我們決定將函數(shù)改為使用 Trait 限定 AsRef<[usize]>,
// 即 impl AsRef<[usize]>:
// fn foo(v: impl AsRef<[usize]>) {
// 	    // 處理 v 的代碼
// 			// ...
// }

fn main() {
  let iter = vec![1, 2, 3].into_iter();
  foo(&iter.collect());
}

// 在原始版本中,編譯器可以推斷出 iter.collect() 應該收集為一個 Vec<usize> 類型,
// 因為我們將其傳遞給了接受 &Vec<usize> 的 foo 函數(shù)。
// 然而,在更改為使用特質限定后,編譯器只知道 foo 函數(shù)
// 接受一個實現(xiàn)了 AsRef<[usize]> 特質的類型。
// 這里有多個類型滿足這個條件,例如 Vec<usize> 和 &[usize]。
// 因此,編譯器無法確定應該將 iter.collect() 的結果解釋為哪個具體類型。
// 這樣的更改將導致編譯器無法推斷類型,并且調用者的代碼將無法通過編譯。

// 為了解決這個問題,調用者可能需要顯示指定期望的類型,例如:
// let iter = vec![1, 2, 3].into_iter();
// foo(&iter.collect::<Vec<usize>>());

泛型的優(yōu)點

  • 可復用:泛型函數(shù)能應用在廣泛的類型上,同時明確給出了這些類型的必須滿足的關系。

  • 靜態(tài)分派和編譯器優(yōu)化: 每個泛型函數(shù)都被專門用于實現(xiàn)了 trait bounds 的具體的類型 (即 單態(tài)化 monomorphized ),這意味著:

    1. 調用的 trait 方法是靜態(tài)生成的,因此是直接對 trait 實現(xiàn)的調用
    2. 編譯器能對這些調用做內聯(lián) (inline) 和其他優(yōu)化
  • 內聯(lián)式布局:如果結構體和枚舉體類型具有某個泛型參數(shù) T , T 的值將在結構體和枚舉體里以內聯(lián)方式排列,不產生任何間接調用。

  • 可推斷:由于泛型函數(shù)的類型參數(shù)通常是推斷出來的, 泛型函數(shù)可以減少復雜的代碼,比如顯式轉換、通常必須的一些方法調用。

  • 精確的類型:因為泛型給實現(xiàn)了某個 trait 的具體類型一個名稱, 從而有可能清楚這個類型需要或創(chuàng)建的地方在哪。比如這個函數(shù):

    fn binary<T: Trait>(x: T, y: T) -> T
    

    會保證消耗和創(chuàng)建具有相同類型 T 的值;不可能傳入實現(xiàn)了 Trait 的但不同名稱的兩個類型。

泛型的缺點

  • 增加代碼大小:單態(tài)化泛型函數(shù)意味著函數(shù)體會被復制。 增加代碼大小和靜態(tài)分派的性能優(yōu)勢之間必須做出衡量。
  • 類型同質化:這是 “精確的類型” 帶來的另一面: 如果 T 是類型參數(shù),那么它代表一個單獨的實際類型。 對于像 Vec<T> 這樣具體的單獨的元素類型也是一樣, 而且 Vec 實際上為了內聯(lián)這些元素,進行了專門的處理。 有時候,不同的類型會更有用,參考 trait objects 。
  • 簽名冗余:過度使用泛型會造成閱讀和理解函數(shù)簽名更困難。

The Rust RFC Book:https://rust-lang.github.io/rfcs/introduction.html

對象安全(Object Safety)

  • 定義 Trait 時,它是否對象安全,也是契約未寫明的一部分
  • 如果 Trait 是對象安全的:
    • 可使用 dyn Trait 將實現(xiàn)該 Trait 的不同類型視為單一通用類型
  • 如果 Trait 不是對象安全的:
    • 編譯器會禁止使用 dyn Traie
  • 建議 Trait 是對象安全的(即使稍微降低使用的便利程度):
    • 提供了使用的新方式和靈活性

對象安全:描述一個 Trait 可否安全的包裝成 Trait Object

對象安全的 Trait 是滿足以下條件的 Trait(RFC 255):

  • 所有的 supertrait 必須是對象安全的
  • Sized 不能作為 supertrait(不能要求 Self: Sized)
  • 不能有任何關聯(lián)常量
  • 不能有任何帶有泛型的關聯(lián)類型
  • 所有的關聯(lián)函數(shù)必須滿足以下條件之一:
    • 可以從 Trait 對象分發(fā)的函數(shù)(Dispatchable functions):
      • 沒有任何類型參數(shù)(生命周期參數(shù)是允許的)
      • 是一個方法,只在接收器類型中使用 Self
      • 接收器是以下類型之一:
        • &Self(即 &self)
        • &mut Self(即 &mut self
        • Box<Self>
        • Rc<Self>
        • Arc<Self>
        • Pin<P>,其中 P 是上述類型之一
      • 沒有 where Self: Sized 約束(Self 的接收器類型(即 self)暗含了這一點)
    • 顯示不可分發(fā)的函數(shù)(non-dispatchable functions)要求:
      • 具有 where Self: Sized 約束(Self 的接收器類型(即 self)暗含了這一點)

例子六

// 假設我們有一個 Animal 特征,它有兩個方法:name 和 speak。
// name 方法返回一個&str,表示動物的名字;
// speak 方法打印出動物發(fā)出的聲音。
// 我們可以為 Dog 和 Cat 類型實現(xiàn)這個特征:
trait Animal {
  fn name(&self) -> &str;
  fn speak(&self);
}

struct Dog {
  name: String,
}

impl Animal for Dog {
  fn name(&self) -> &str {
    &self.name
  }
  
  fn speak(&self) {
    println!("Woof!");
  }
}

struct Cat {
  name: String,
}

impl Animal for Cat {
  fn name(&self) -> &str {
    &self.name
  }
  
  fn speak(&self) {
    println!("Meow!");
  }
}

// 這個 Animal 特征是 object-safe 的,因為它沒有返回 Self 類型或使用泛型參數(shù)。
// 所以我們可以用它來創(chuàng)建一個 trait object:
fn main() {
  let dog = Dog {
    name: "Fido".to_string(),
  };
  let cat = Cat {
    name: "Whiskers".to_string(),
  };
  
  let animals: Vec<&dyn Animal> = vec![&dog, &cat];
  
  for animal in animals {
    println!("This is {}", animal.name());
    animal.speak();
  }
}
// 這樣我們就可以用一個統(tǒng)一的類型 Vec<&dyn Animal> 來存儲不同類型的動物,
// 并且通過 trait object 來調用它們的方法。

例子七

// 但是如果我們給 Animal 特征添加一個新的方法 clone,它返回一個 Self 類型:
trait Animal {
  fn name(&self) -> &str;
  fn speak(&self);
  fn clone(&self) -> Self;
}
// 那么這個特征就不再是 object-safe 的了,
// 因為 clone 方法違反了規(guī)則:返回類型不能是 Self。
// 這樣我們就不能用它來創(chuàng)建 trait object 了,
// 因為編譯器無法知道 Self 具體指代哪個類型

struct Dog {
  name: String,
}

impl Animal for Dog {
  fn name(&self) -> &str {
    &self.name
  }
  
  fn speak(&self) {
    println!("Woof!");
  }
  
  fn clone(&self) -> Self
  where
  		Self: Sized,
  {
    todo!()
  }
}

struct Cat {
  name: String,
}

impl Animal for Cat {
  fn name(&self) -> &str {
    &self.name
  }
  
  fn speak(&self) {
    println!("Meow!");
  }
  
  fn clone(&self) -> Self
  where
  		Self: Sized,
  {
    todo!()
  }
}

fn main() {
  let dog = Dog {
    name: "Fido".to_string(),
  };
  let cat = Cat {
    name: "Whiskers".to_string(),
  };
  
  let animals: Vec<&dyn Animal> = vec![&dog, &cat]; // 報錯 the trait `Animal` cannot be made into an object consider moving `clone` to another trait
  
  for animal in animals {
    println!("This is {}", animal.name());
    animal.speak();
  }
}

例子八

// 如果我們想讓 Animal 特征保持 object-safe,
// 我們就不能給它添加返回 Self 類型的方法。
// 或者,我們可以給 clone 方法添加一個 where Self: Sized 的特征界定,
// 這樣他就只能在具體類型上調用,而不是在 trait object 上:
trait Animal {
  fn name(&self) -> &str;
  fn speak(&self);
  fn clone(&self) -> Self
  where
  		Self: Sized;
}

struct Dog {
  name: String,
}

impl Animal for Dog {
  fn name(&self) -> &str {
    &self.name
  }
  
  fn speak(&self) {
    println!("Woof!");
  }
  
  fn clone(&self) -> Self
  where
  		Self: Sized,
  {
    todo!()
  }
}

struct Cat {
  name: String,
}

impl Animal for Cat {
  fn name(&self) -> &str {
    &self.name
  }
  
  fn speak(&self) {
    println!("Meow!");
  }
  
  fn clone(&self) -> Self
  where
  		Self: Sized,
  {
    todo!()
  }
}

// 這樣我們就可以繼續(xù)用 Animal 特征來創(chuàng)建 trait object 了,
// 但是我們不能用 trait object 來調用 clone 方法
fn main() {
  let dog = Dog {
    name: "Fido".to_string(),
  };
  let cat = Cat {
    name: "Whiskers".to_string(),
  };
  
  cat.clone(); // 只能在具體的類型上調用
  
  let animals: Vec<&dyn Animal> = vec![&dog, &cat]; 
  
  for animal in animals {
    println!("This is {}", animal.name());
    animal.speak();
    animal.clone(); // 報錯 the `clone` method cannot be invoked on a trait object 
  }
}
  • 如果 Trait 必須有泛型方法,考慮:
    • 泛型參數(shù)放在 Trait 上
    • 泛型參數(shù)可否使用動態(tài)分發(fā),來保證Trait 的對象安全

例子九

use std::collections::HashSet;
use std::hash::Hash;
// 將泛型參數(shù)放在 Trait 本身上
trait Container<T> {
  fn contains(&self, item: &T) -> bool;
}

// 我們可以為不同的容器類型實現(xiàn) Container Trait,每個實現(xiàn)都具有自己特定的元素類型。
// 例,我們可以為 Vec<T> 和 HashSet<T> 實現(xiàn) Container Trait:
impl<T> Container<T> for Vec<T>
where
		T: PartialEq,
{
  fn contains(&self, item: &T) -> bool {
    self.iter().any(|x| x == item)
  }
}

impl<T> Container<T> for HashSet<T>
where
		T: Hash + Eq,
{
  fn contains(&self, item: &T) -> bool {
    self.contains(item)
  }
}

fn main() {
  // 創(chuàng)建一個 Vec<T> 和 HashSet<T> 的實例
  let vec_container: Box<dyn Container<i32>> = Box::new(vec![1, 2, 3]);
  let hashset_container: Box<dyn Container<i32>> = Box::new(vec![4, 5, 6].into_iter().collect::<HashSet<_>>());
  
  // 調用 contains 方法
  println!("Vec contains 2: {}", vec_container.contains(&2));
  println!("HashSet contains 6: {}", hashset_container.contains(&6));
}

例子十

use std::fmt::Debug;
// 假設我們有一個 Trait Foo,它有一個泛型方法 bar,它接受一個泛型參數(shù) T:
// trait Foo {
//		fn bar<T>(&self, x: T);
//}

// 這個 Trait 是不是 object-safe 的呢?答案是:取決于 T 的類型。  注意:它不是對象安全的
// 如果 T 是一個具體類型,比如 i32或 String,那么它就不是 object-safe 的,
// 因為它需要在運行時知道 T 的具體類型才能調用 bar 方法。
// 但如果 T 也是一個 trait object,比如 &dyn Debug 或 &dyn Display,
// 那么這個 Trait 就是 object-safe 的,因為它可以用動態(tài)分發(fā)的方式來調用 T 的方法。
// 所以我們可以這樣寫:
trait Foo {
  fn bar(&self, x: &dyn Debug);
}

// 定義一個結構體 A,它實現(xiàn)了 Foo 特征
struct A {
  name: String,
}

impl Foo for A {
  fn bar(&self, x: &dyn Debug) {
    println!("A {} says {:?}", self.name, x);
  }
}

// 定義一個結構體 B,它也實現(xiàn)了 Foo 特征
struct B {
  id: i32,
}

impl Foo for B {
  fn bar(&self, x: &dyn Debug) {
    println!("B {} says {:?}", self.id, x);
  }
}

// 這樣我們就可以用 Foo 特征來創(chuàng)建 trait object 了,比如:
fn main() {
  // 創(chuàng)建兩個不同類型的值,它們都實現(xiàn)了 Foo 特征
  let a = A {
    name: "Alice".to_string(),
  };
  let b = B { id: 42};
  
  // 創(chuàng)建一個 Vec,它存儲了 Foo 的 trait object
  let foos: Vec<&dyn Foo> = vec![&a, &b];
  
  // 遍歷 Vec,并用 trait object 調用 bar 方法
  for foo in foos {
    foo.bar(&"Hello"); // "Hello" 實現(xiàn)了 Debug 特征
  }
}
  • 為實現(xiàn)對象安全,需要做出多大犧牲?
    • 考慮你的 Trait 會被怎樣使用,用戶是否想把它當做 Trait 對象
      • 用戶想使用你的 Trait 的多種不同實例 -> 努力實現(xiàn)對象安全

借用 VS 擁有(Borrowed vs Owned)

  • 針對 Rust 中幾乎每個函數(shù)、Trait 和類型,須決定:
    • 是否應該擁有數(shù)據(jù)
    • 僅持有對數(shù)據(jù)的引用
  • 如果代碼需要數(shù)據(jù)的所有權:
    • 它必須存儲擁有的數(shù)據(jù)
  • 當你的代碼必須擁有數(shù)據(jù)時:
    • 必須讓調用者提供擁有的數(shù)據(jù),而不是引用或克隆
  • 這樣可讓調用者控制分配,并且可清楚地看到使用相關接口的成本
  • 如果代碼不需擁有數(shù)據(jù):
    • 應操作于引用
  • 例外:
    • 像 i32、bool、f64 等 “小類型”
      • 直接存儲和復制的成本與通過引用存儲的成本相同
      • 并不是所有 Copy 類型都適用:
        • 例:[u8; 8192] 是 Copy 類型,但在多個地方存儲和復制它會很昂貴
  • 無法確定代碼是否需要擁有數(shù)據(jù),因為它取決于運行時情況
  • Cow 類型:
    • 允許在需要時持有引用或擁有值
  • 如果只有引用的情況下要求生成擁有的值:
    • Cow 將使用 ToOwned trait 在后臺創(chuàng)建一個,通常是通過克隆
  • 通常在返回類型中使用 Cow 來表示有時會分配內存的函數(shù)

例子十一

use std::borrow::Cow;

// 假設我們有一個函數(shù) process_data,它接收一個字符串參數(shù),
// 并根據(jù)一些條件對其進行處理。有時,我們需要修改輸入字符串,
// 并擁有對修改后的字符串的所有權。
// 然而,大多數(shù)情況下,我們只是對輸入字符串進行讀取操作,而不需要修改它。
fn process_data(data: Cow<str>) {
  if data.contains("invalid") {
    // 如果輸入字符串包含 “invalid”,我們需要修改它
    let owned_data: String = data.into_owned();
    // 進行一些修改操作
    println!("Processed data: {}", owned_data);
  } else {
    // 如果輸入字符串不包含 “invalid”,我們只需要讀取它
    println!("Data: {}", data);
  }
}

// 在這個例子中,我們使用了 Cow<str> 類型作為參數(shù)類型。
// 當調用函數(shù)時,我們可以傳遞一個普通的字符串引用(&str)
// 或一個擁有所有權的字符串(String)作為參數(shù)。
fn main() {
  let input1 = "This is valid data.";
  process_data(Cow::Borrowed(input1));
  
  let input2 = "This is invalid data.";
  process_data(Cow::Owned(input2.to_owned()));
}
  • 有時,引用生命周期會讓接口復雜,難以使用
    • 如果用戶使用接口時遇到編譯問題,這表明您可能需要(即使不必要)擁有某些數(shù)據(jù)的所有權
      • 這樣做的話,建議首先考慮容易克隆或不涉及性能敏感性的數(shù)據(jù),而不是直接對大塊數(shù)據(jù)的內容進行堆分配
      • 這樣做可以避免性能問題并提高接口的可用性

可失敗和阻塞的析構函數(shù)(Fallible and Blocking Destructors)

  • 析構函數(shù)(Destructor):在值被銷毀時執(zhí)行特定的清理操作
  • 析構函數(shù)由 Drop trait 實現(xiàn):它定義了一個 drop 方法
  • 析構函數(shù)通常是不允許失敗的,并且是非阻塞執(zhí)行的。但有時:
    • 例如釋放資源時,可能需要關閉網(wǎng)絡連接或寫入日志文件,這些操作都有可能發(fā)生錯誤
    • 可能需要執(zhí)行阻塞操作,例如等待一個線程的結束或等待一個異步任務的完成
  • 針對 I/O 操作的類型,在丟棄時需要執(zhí)行清理
    • 例:將寫入的數(shù)據(jù)刷新到磁盤、關閉打開的文件、斷開網(wǎng)絡連接
  • 這些清理操作應在類型的 Drop 實現(xiàn)中完成
    • 問題:一旦值被丟棄,就無法向用戶傳遞錯誤信息,除非通過 panic
    • 異步代碼也有類似問題:希望在清理過程中完成這些工作,但有其他工作處于 pending 狀態(tài)
      • 可嘗試啟動另一個執(zhí)行器,但這會引入其他問題,例如在異步代碼中阻塞
  • 沒有完美解決方案:需要通過 Drop 盡力清理
    • 如果清理出錯了,至少我們嘗試了 —— 忽略錯誤并繼續(xù)
    • 如果還有可用的執(zhí)行器,可嘗試生成一個 future 來做清理,但如果 future 永不會運行,我們也盡力了
  • 若用戶不想留下“松散” 線程:提供顯式的析構函數(shù)
    • 這通常是一個方法,它獲得 self 的所有權并暴露任何錯誤(使用 -> Result<_, _>)或異步性(使用 async fn),這些都是與銷毀相關的

例子十二

use std::os::fd::AsRawFd;

// 一個表示文件句柄的類型
struct File {
  // 文件名
  name: String,
  // 文件描述符
  fd: i32,
}

// File 類型的方法實現(xiàn)
impl File {
  // 一個構造函數(shù),打開一個文件并返回一個 File 實例
  fn open(name: &str) -> Result<File, std::io::Error> {
    // 使用 std::fs::OpenOptions 打開文件,具有讀寫權限
    let file = std::fs::OpenOptions::new().read(true).write(true).open(name)?;
    // 使用 std::os::unix::io::AsRawFd 獲取文件描述符
    let fd = file.as_raw_fd();
    // 返回一個 File 實例,包含 name 和 fd 字段
    Ok(File {
      name: name.to_string(),
      fd,
    })
  }
  
  // 一個顯式的析構器,關閉文件并返回任何錯誤
  fn close(self) -> Result<(), std::io::Error> {
    // 使用 std::os::unix::io::FromRawFd 將 fd 轉換回 std::fs::File
    let file: std::fs::File = unsafe { std::os::unix::io::FromRawFd::from_raw_fd(self.id) };
    // 使用 std::fs::File::sync_all 將任何掛起的寫入刷新到磁盤
    file.sync_all()?;
    // 使用 std::fs::File::set_len 將文件截斷為零字節(jié)
    file.set_len(0)?;
    // 再次使用 std::fs::File::sync_all 刷新截斷
    file.sync_all()?;
    // 丟棄 file 實例,它會自動關閉
    drop(file);
    // 返回 Ok(())
    Ok(())
  }
}

// 一個測試 File 類型的主函數(shù)
fn main() {
  // 創(chuàng)建一個名為 "test.txt" 的文件,包含一些內容
  std::fs::write("test.txt", "Hello, world!").unwrap();
  // 打開文件并獲取一個 File 實例
  let file = File::open("test.txt").unwrap();
  // 打印文件名和 fd
  println!("File name: {}, fd: {}", file.name, file.fd);
  // 關閉文件并處理任何錯誤
  match file.close() {
    Ok(()) => println!("File closed successfully"),
    Err(e) => println!("Error closing file: {}", e),
  }
  // 檢查關閉后的文件大小
  let metadata = std::fs::metadata("test.txt").unwrap();
  println!("File size: {} bytes", metadata.len());
}

注意:顯式的析構函數(shù)需要在文檔中突出顯示

  • 添加顯式析構函數(shù)時會遇問題:
    • 當類型實現(xiàn)了 Drop,在析構函數(shù)中無法將該類型的任何字段移出
      • 因為在顯式析構函數(shù)運行后,Drop::drop 仍會被調用,它接收 &mut self,要求 self 的所有部分都沒有被移動
    • Drop 接受的是 &mut self,而不是 self,因此 Drop 無法實現(xiàn)簡單地調用顯式析構函數(shù)并忽略其結果(因為 Drop 不擁有 self)

例子十三

use std::os::fd::AsRawFd;

// 一個表示文件句柄的類型
struct File {
  // 文件名
  name: String,
  // 文件描述符
  fd: i32,
}

// File 類型的方法實現(xiàn)
impl File {
  // 一個構造函數(shù),打開一個文件并返回一個 File 實例
  fn open(name: &str) -> Result<File, std::io::Error> {
    // 使用 std::fs::OpenOptions 打開文件,具有讀寫權限
    let file = std::fs::OpenOptions::new().read(true).write(true).open(name)?;
    // 使用 std::os::unix::io::AsRawFd 獲取文件描述符
    let fd = file.as_raw_fd();
    // 返回一個 File 實例,包含 name 和 fd 字段
    Ok(File {
      name: name.to_string(),
      fd,
    })
  }
  
  // 一個顯式的析構器,關閉文件并返回任何錯誤
  fn close(self) -> Result<(), std::io::Error> {
    // 移出 name 字段并打印它
    let name = self.name; // 報錯 不能從 `self.name` 中移出值,因為它位于 `&mut` 引用后面
    println!("Closing file {}", name);
    // 使用 std::os::unix::io::FromRawFd 將 fd 轉換回 std::fs::File
    let file: std::fs::File = unsafe { std::os::unix::io::FromRawFd::from_raw_fd(self.id) };
    // 使用 std::fs::File::sync_all 將任何掛起的寫入刷新到磁盤
    file.sync_all()?;
    // 使用 std::fs::File::set_len 將文件截斷為零字節(jié)
    file.set_len(0)?;
    // 再次使用 std::fs::File::sync_all 刷新截斷
    file.sync_all()?;
    // 丟棄 file 實例,它會自動關閉
    drop(file);
    // 返回 Ok(())
    Ok(())
  }
}

// Drop trait 的實現(xiàn),用于在值離開作用域時運行一些代碼
impl Drop for File {
  // drop 方法,接受一個可變引用到 self 作為參數(shù)
  fn drop(&mut self) {
    // 調用 close 方法并忽略它的結果
    let _ = self.close(); // 報錯 不能從 `*self` 中移出值,因為它位于 `&mut` 引用后面
    // 打印一條消息,表明文件被丟棄了
    println!("Dropping file {}", self.name);
  }
}

// 一個測試 File 類型的主函數(shù)
fn main() {
  // 創(chuàng)建一個名為 "test.txt" 的文件,包含一些內容
  std::fs::write("test.txt", "Hello, world!").unwrap();
  // 打開文件并獲取一個 File 實例
  let file = File::open("test.txt").unwrap();
  // 打印文件名和 fd
  println!("File name: {}, fd: {}", file.name, file.fd);
  // 關閉文件并處理任何錯誤
  match file.close() {
    Ok(()) => println!("File closed successfully"),
    Err(e) => println!("Error closing file: {}", e),
  }
  // 檢查關閉后的文件大小
  let metadata = std::fs::metadata("test.txt").unwrap();
  println!("File size: {} bytes", metadata.len());
}
  • 解決辦法(沒有完美的),方法之一 :
    • 將頂層類型作為包裝了 Option 的新類型,Option 持有一個內部類型,該類型包含所有的字段
    • 在兩個析構函數(shù)中使用 Option::take;當內部類型還沒有被取走時,調用內部類型的顯式析構函數(shù)
    • 由于內部類型沒有實現(xiàn) Drop,你可以獲取所有字段的所有權
    • 缺點:想在頂層類型上提供所有的方法,都必須包含通過 Option 來獲取內部類型上字段的代碼

例子十四

use std::os::fd::AsRawFd;

// 一個表示文件句柄的類型
struct File {
  // 一個包裝在 Option 中的內部類型
  inner: Option<InnerFile>,
}

// 一個內部類型,持有文件名和文件描述符
struct InnerFile {
  // 文件名
  name: String,
  // 文件描述符
  fd: i32,
}

// File 類型的方法實現(xiàn)
impl File {
  // 一個構造函數(shù),打開一個文件并返回一個 File 實例
  fn open(name: &str) -> Result<File, std::io::Error> {
    // 使用 std::fs::OpenOptions 打開文件,具有讀寫權限
    let file = std::fs::OpenOptions::new().read(true).write(true).open(name)?;
    // 使用 std::os::unix::io::AsRawFd 獲取文件描述符
    let fd = file.as_raw_fd();
    // 返回一個 File 實例,包含一個 Some(InnerFile) 的 inner 字段
    Ok(File {
      inner: Some(InnerFile {
        name: name.to_string(),
        fd,
      }),
    })
  }
  
  // 一個顯式的析構器,關閉文件并返回任何錯誤
  fn close(mut self) -> Result<(), std::io::Error> {
    // 使用 Option::take 取出 inner 字段的值,并檢查是否是 Some(InnerFile)
    if let Some(inner) = self.inner.take() {
      // 移出 name 和 fd 字段并打印它們
      let name = inner.name;
      let fd = inner.fd;
      println!("Closing file {} with fd {}", name, fd);
      // 使用 std::os::unix::io::FromRawFd 將 fd 轉換回 std::fs::File
      let file: std::fs::File = unsafe { std::os::unix::io::FromRawFd::from_raw_fd(self.id) };
      // 使用 std::fs::File::sync_all 將任何掛起的寫入刷新到磁盤
      file.sync_all()?;
      // 使用 std::fs::File::set_len 將文件截斷為零字節(jié)
      file.set_len(0)?;
      // 再次使用 std::fs::File::sync_all 刷新截斷
      file.sync_all()?;
      // 丟棄 file 實例,它會自動關閉
      drop(file);
      // 返回 Ok(())
      Ok(())
    } else {
      // 如果 inner 字段是 None,說明文件已經(jīng)被關閉或丟棄,返回一個錯誤
      Err(std::io::Error::new(
        std::io::ErrorKind::Other,
        "File already closed or dropped",
      ))
    }
  }
}

// Drop trait 的實現(xiàn),用于在值離開作用域時運行一些代碼
impl Drop for File {
  // drop 方法,接受一個可變引用到 self 作為參數(shù)
  fn drop(&mut self) {
    // 使用 Option::take 取出 inner 字段的值,并檢查是否是 Some(InnerFile)
    if let Some(inner) = self.inner.take() {
      // 移出 name 和 fd 字段并打印它們
      let name = inner.name;
      let fd = inner.id;
      println!("Dropping file {} with fd {}", name, fd);
      // 使用 std::os::unix::io::FromRawFd 將 fd 轉換回 std::fs::File
      let file: std::fs::File = unsafe { std::os::unix::io::FromRawFd::from_raw_fd(fd) };
      // 丟棄 file 實例,它會自動關閉
      drop(file);
    } else {
      // 如果 inner 字段是 None,說明文件已經(jīng)被關閉或丟棄,不做任何操作
    }
  }
}

// 一個測試 File 類型的主函數(shù)
fn main() {
  // 創(chuàng)建一個名為 "test.txt" 的文件,包含一些內容
  std::fs::write("test.txt", "Hello, world!").unwrap();
  // 打開文件并獲取一個 File 實例
  let file = File::open("test.txt").unwrap();
  // 打印文件名和 fd
  println!(
    "File name: {}, fd: {}", 
    file.inner.as_ref().unwrap().name, 
    file.inner.as_ref().unwrap().fd
  );
  // 關閉文件并處理任何錯誤
  match file.close() {
    Ok(()) => println!("File closed successfully"),
    Err(e) => println!("Error closing file: {}", e),
  }
  // 檢查關閉后的文件大小
  let metadata = std::fs::metadata("test.txt").unwrap();
  println!("File size: {} bytes", metadata.len());
}
  • 方法二:
    • 所有字段都可以 take
    • 如果類型具有合理的 ”空“ 值,那么效果很好
    • 如果您必須將幾乎每個字段都包裝在 Option 中,然后對這些字段的每次訪問都進行匹配的 unwrap,很繁瑣

例子十五

use std::os::fd::AsRawFd;

// 一個表示文件句柄的類型
struct File {
  // 文件名,包裝在一個 Option 中
  name: Option<String>,
  // 文件描述符,包裝在一個 Option 中
  fd: Option<i32>,
}

// File 類型的方法實現(xiàn)
impl File {
  // 一個構造函數(shù),打開一個文件并返回一個 File 實例
  fn open(name: &str) -> Result<File, std::io::Error> {
    // 使用 std::fs::OpenOptions 打開文件,具有讀寫權限
    let file = std::fs::OpenOptions::new().read(true).write(true).open(name)?;
    // 使用 std::os::unix::io::AsRawFd 獲取文件描述符
    let fd = file.as_raw_fd();
    // 返回一個 File 實例,包含一個 Some(name) 和一個 Some(fd) 的字段
    Ok(File {
      name: Some(name.to_string()),
      fd: Some(fd),
    })
  }
  
  // 一個顯式的析構器,關閉文件并返回任何錯誤
  fn close(mut self) -> Result<(), std::io::Error> {
    // 使用 std::mem::take 取出 name 字段的值,并檢查是否是 Some(name)
    if let Some(name) = std::mem::take(&mut self.name) {
      // 使用 std::mem::take 取出 fd 字段的值,并檢查是否是 Some(fd)
      if let Some(fd) = std::mem::take(&mut self.fd) {
        // 打印文件名和文件描述符
        println!("Closing file {} with fd {}", name, fd);
        // 使用 std::os::unix::io::FromRawFd 將 fd 轉換回 std::fs::File
        let file: std::fs::File = unsafe { std::os::unix::io::FromRawFd::from_raw_fd(fd) };
        // 使用 std::fs::File::sync_all 將任何掛起的寫入刷新到磁盤
        file.sync_all()?;
        // 使用 std::fs::File::set_len 將文件截斷為零字節(jié)
        file.set_len(0)?;
        // 再次使用 std::fs::File::sync_all 刷新截斷
        file.sync_all()?;
        // 丟棄 file 實例,它會自動關閉
        drop(file);
        // 返回 Ok(())
        Ok(())
      } else {
        // 如果 fd 字段是 None,說明文件已經(jīng)被關閉或丟棄,返回一個錯誤
        Err(std::io::Error::new(
          std::io::ErrorKind::Other,
          "File descriptor already taken or dropped",
      ))
    }
  } else {
    // 如果 name 字段是 None,說明文件已經(jīng)被關閉或丟棄,返回一個錯誤
    Err(std::io::Error::new(
      std::io::ErrorKind::Other,
      "File name already taken or dropped",
    ))
    }
  }
}

// Drop trait 的實現(xiàn),用于在值離開作用域時運行一些代碼
impl Drop for File {
  // drop 方法,接受一個可變引用到 self 作為參數(shù)
  fn drop(&mut self) {
    // 使用 std::mem::take 取出 name 字段的值,并檢查是否是 Some(name)
    if let Some(name) = std::mem::take(&mut self.name) {
      // 使用 std::mem::take 取出 fd 字段的值,并檢查是否是 Some(fd)
      if let Some(fd) = std::mem::take(&mut self.fd) {
        // 打印文件名和文件描述符
        println!("Dropping file {} with fd {}", name, fd);
        // 使用 std::os::unix::io::FromRawFd 將 fd 轉換回 std::fs::File
        let file: std::fs::File = unsafe { std::os::unix::io::FromRawFd::from_raw_fd(fd) };
        // 丟棄 file 實例,它會自動關閉
        drop(file);
      } else {
        // 如果 fd 字段是 None,說明文件已經(jīng)被關閉或丟棄,不做任何操作
      }
    } else {
      // 如果 name 字段是 None,說明文件已經(jīng)被關閉或丟棄,不做任何操作
    }
  }
}

// 一個測試 File 類型的主函數(shù)
fn main() {
  // 創(chuàng)建一個名為 "test.txt" 的文件,包含一些內容
  std::fs::write("test.txt", "Hello, world!").unwrap();
  // 打開文件并獲取一個 File 實例
  let file = File::open("test.txt").unwrap();
  // 打印文件名和 fd
  println!(
    "File name: {}, fd: {}", 
    file.inner.as_ref().unwrap().name, 
    file.inner.as_ref().unwrap().fd
  );
  // 關閉文件并處理任何錯誤
  match file.close() {
    Ok(()) => println!("File closed successfully"),
    Err(e) => println!("Error closing file: {}", e),
  }
  // 檢查關閉后的文件大小
  let metadata = std::fs::metadata("test.txt").unwrap();
  println!("File size: {} bytes", metadata.len());
}
  • 方法三:
    • 將數(shù)據(jù)持有在 ManuallyDrop 類型內,它會解引用內部類型,不必再 unwrap
    • 在 drop 中銷毀時,可用 ManuallyDrop::take 來獲取所有權
    • 缺點:ManuallyDrop::take 是 unsafe 的

例子十六文章來源地址http://www.zghlxwxcb.cn/news/detail-477348.html

// 引入 std 庫中的一些模塊
use std::{mem::ManuallyDrop, os::fd::AsRawFd};

// 定義一個表示文件句柄的結構體
struct File {
  // 文件名,包裝在一個 ManuallyDrop 中
  name: ManuallyDrop<String>,
  // 文件描述符,包裝在一個 ManuallyDrop 中
  fd: ManuallyDrop<i32>,
}

// 為 File 結構體實現(xiàn)一些方法
impl File {
  // 一個構造函數(shù),打開一個文件并返回一個 File 實例
  fn open(name: &str) -> Result<File, std::io::Error> {
    // 使用 std::fs::OpenOptions 打開文件,具有讀寫權限
    let file = std::fs::OpenOptions::new().read(true).write(true).open(name)?;
    // 使用 std::os::unix::io::AsRawFd 獲取文件描述符
    let fd = file.as_raw_fd();
    // 返回一個 File 實例,包含一個 ManuallyDrop(name) 和一個 ManuallyDrop(fd) 的字段
    Ok(File {
      name: ManuallyDrop::new(name.to_string()),
      fd: ManuallyDrop::new(fd),
    })
  }
  
  // 一個顯式的析構器,關閉文件并返回任何錯誤
  fn close(mut self) -> Result<(), std::io::Error> {
    // 使用 std::mem::replace 將 name 字段替換為一個空字符串,并獲取原來的值
    if let name = std::mem::replace(&mut self.name, ManuallyDrop::new(String::new())); 
    // 使用 std::mem::replace 將 fd 字段替換為一個無效的值,并獲取原來的值
    if let fd = std::mem::replace(&mut self.fd, ManuallyDrop::new(-1)); 
    // 打印文件名和文件描述符
    println!("Closing file {:?} with fd {:?}", name, fd);
    // 使用 std::os::unix::io::FromRawFd 將 fd 轉換回 std::fs::File
    let file: std::fs::File = unsafe { std::os::unix::io::FromRawFd::from_raw_fd(*fd) };
    // 使用 std::fs::File::sync_all 將任何掛起的寫入刷新到磁盤
    file.sync_all()?;
    // 使用 std::fs::File::set_len 將文件截斷為零字節(jié)
    file.set_len(0)?;
    // 再次使用 std::fs::File::sync_all 刷新截斷
    file.sync_all()?;
    // 丟棄 file 實例,它會自動關閉
    drop(file);
    // 返回 Ok(())
    Ok(())
  }
}

// 為 File 結構體實現(xiàn) Drop trait,用于在值離開作用域時運行一些代碼
impl Drop for File {
  // drop 方法,接受一個可變引用到 self 作為參數(shù)
  fn drop(&mut self) {
    // 使用 ManuallyDrop::take 取出 name 字段的值,并檢查是否是空字符串
    let name = unsafe { ManuallyDrop::take(&mut self.name) };
    // 使用 ManuallyDrop::take 取出 fd 字段的值,并檢查是否是無效的值
    let fd = unsafe { ManuallyDrop::take(&mut self.id) };
    // 打印文件名和文件描述符
    println!("Dropping file {:?} with fd {:?}", name, fd);
    
    // 如果 fd 字段不是無效的值,說明文件還沒有被關閉或丟棄,需要執(zhí)行一些操作
    if fd != -1 {
      // 使用 std::os::unix::io::FromRawFd 將 fd 轉換回 std::fs::File
      let file: std::fs::File = unsafe { std::os::unix::io::FromRawFd::from_raw_fd(fd) };
      // 丟棄 file 實例,它會自動關閉
      drop(file);
    }
  }
}

// 一個測試 File 類型的主函數(shù)
fn main() {
  // 創(chuàng)建一個名為 "test.txt" 的文件,包含一些內容
  std::fs::write("test.txt", "Hello, world!").unwrap();
  // 打開文件并獲取一個 File 實例
  let file = File::open("test.txt").unwrap();
  // 打印文件名和 fd
  println!(
    "File name: {}, fd: {}", 
    *file.name, 
    *file.fd
  );
  // 關閉文件并處理任何錯誤
  match file.close() {
    Ok(()) => println!("File closed successfully"),
    Err(e) => println!("Error closing file: {}", e),
  }
  // 檢查關閉后的文件大小
  let metadata = std::fs::metadata("test.txt").unwrap();
  println!("File size: {} bytes", metadata.len());
}
  • 根據(jù)實際情況選擇方案
    • 傾向于選擇第二個方案
      • 只有發(fā)現(xiàn)自己處于一堆 Option 中時才切換到其他選項
    • 如果代碼足夠簡單,可輕松檢查代碼安全性,那么 ManuallyDrop 方案也挺好

到了這里,關于Rust - 接口設計建議之靈活(flexible)的文章就介紹完了。如果您還想了解更多內容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來自互聯(lián)網(wǎng)用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。如若轉載,請注明出處: 如若內容造成侵權/違法違規(guī)/事實不符,請點擊違法舉報進行投訴反饋,一經(jīng)查實,立即刪除!

領支付寶紅包贊助服務器費用

相關文章

  • 【Rust 基礎篇】Rust 模式:高效、安全和靈活的匹配工具

    在編程中,經(jīng)常需要對數(shù)據(jù)進行匹配和處理,例如從一個復雜的數(shù)據(jù)結構中提取特定的值,或者根據(jù)不同的情況執(zhí)行不同的邏輯。Rust是一門現(xiàn)代的系統(tǒng)編程語言,它引入了一種稱為\\\"模式\\\"(Pattern)的強大特性,使得數(shù)據(jù)的匹配和處理變得高效、安全和靈活。本篇博客將深入探

    2024年02月08日
    瀏覽(32)
  • Rust字符串:安全、高效和靈活的數(shù)據(jù)類型

    Rust字符串:安全、高效和靈活的數(shù)據(jù)類型

    Rust是一種現(xiàn)代的系統(tǒng)級編程語言,以其出色的內存安全性和高性能而受到廣泛關注。在Rust中,字符串是一種重要的數(shù)據(jù)類型,它具有獨特的特點,使其在處理文本和字符數(shù)據(jù)時成為理想的選擇。本文將深入探討Rust字符串的特性,包括安全性、高效性和靈活性,以幫助您更好

    2024年01月19日
    瀏覽(23)
  • 靈活使用Postman環(huán)境變量和全局變量,提高接口測試效率!

    ?前言: 環(huán)境變量和全局變量的概念 環(huán)境變量和全局變量的使用方法 1. 定義變量 2. 使用變量 環(huán)境變量和全局變量的實例代碼

    2024年02月08日
    瀏覽(29)
  • 【C++】string的接口從生澀到靈活使用——這篇文章就夠了

    【C++】string的接口從生澀到靈活使用——這篇文章就夠了

    目錄 第一類題目:反轉字符串類型? 1. 反轉字母(初級) 正向迭代器 ? 題目講解 ? ?2.反轉字母(中級) reverse和size 題目講解 ? 3.反轉字母(高級)? find和resize ? 題目講解 ? 第二類題目:一個字符串的子字符串 1.模擬實現(xiàn)strStr() KMP算法 理論講解 ? 代碼實現(xiàn) ? 使用next數(shù)組

    2024年01月22日
    瀏覽(20)
  • 接口優(yōu)化的目錄(建議收藏)

    接口優(yōu)化的目錄(建議收藏)

    目錄 前言 ?編輯 批處理 優(yōu)點 缺點 場景 同步轉異步? 優(yōu)點 缺點 場景 空間換時間 優(yōu)點 缺點 場景 預處理 優(yōu)點 缺點 場景 池化技術 優(yōu)點 缺點 場景 串行改并行 優(yōu)點 缺點 場景 索引 優(yōu)點 缺點 場景 避免大事務 優(yōu)點 缺點 場景 深度分頁 優(yōu)點 缺點 數(shù)據(jù)一致性問題的特殊處理

    2023年04月09日
    瀏覽(29)
  • 【C#】當重復使用一段代碼倒計時時,定義接口類和通過實現(xiàn)類繼承接口方式進行封裝方法和體現(xiàn)代碼靈活性

    【C#】當重復使用一段代碼倒計時時,定義接口類和通過實現(xiàn)類繼承接口方式進行封裝方法和體現(xiàn)代碼靈活性

    歡迎來到《小5講堂》 大家好,我是全棧小5。 這是《C#》序列文章,每篇文章將以博主理解的角度展開講解, 特別是針對知識點的概念進行敘說,大部分文章將會對這些概念進行實際例子驗證,以此達到加深對知識點的理解和掌握。 溫馨提示:博主能力有限,理解水平有限

    2024年01月19日
    瀏覽(30)
  • 支付寶代扣接口簽約的各種問題排查(建議收藏)

    之前對接支付寶商家扣款的時候,在 簽約協(xié)議 的部分卡了很久,今天把之前遇到的簽約問題匯總記錄一下~ ? 首先幫大家捋一下簽約的順序,便于直觀理解: ? ? 其次還需要知道的是,支付寶的商家扣款的簽約接口有 兩個 : 一個是 單獨簽約 接口: ? ? 另一個是 支付并簽

    2024年02月06日
    瀏覽(16)
  • 6個步驟輕松實現(xiàn) postman 接口壓力測試(建議收藏)

    6個步驟輕松實現(xiàn) postman 接口壓力測試(建議收藏)

    這里講是postman做接口并發(fā)測試,基礎用法不做贅述 最后: 可以在我的VX公眾號:【自動化測試老司機】免費領取一份216頁軟件測試工程師面試寶典文檔資料。以及相對應的視頻學習教程免費分享!,其中包括了有基礎知識、Linux必備、Shell、互聯(lián)網(wǎng)程序原理、Mysql數(shù)據(jù)庫、抓

    2024年02月04日
    瀏覽(26)
  • SD-WAN組網(wǎng)設計原則:靈活、安全、高效

    SD-WAN組網(wǎng)設計原則:靈活、安全、高效

    在實現(xiàn)按需、靈活和安全的 SD-WAN組網(wǎng) 方案中,我們必須遵循一系列關鍵的設計原則,以確保網(wǎng)絡的可靠性和效率。通過以下幾點設計原則,SD-WAN能夠滿足企業(yè)對靈活性、安全性和高效性的迫切需求。 靈活的Overlay網(wǎng)絡互聯(lián) SD-WAN 通過IP地址在站點之間建立Overlay網(wǎng)絡,將Overla

    2024年01月19日
    瀏覽(39)
  • 一文1400字使用Jmeter進行http接口測試【建議收藏】

    一文1400字使用Jmeter進行http接口測試【建議收藏】

    本文主要針對http接口進行測試,使用Jmeter工具實現(xiàn)。Jmter工具設計之初是用于做性能測試的,它在實現(xiàn)對各種接口的調用方面已經(jīng)做的比較成熟,因此,本次直接使用Jmeter工具來完成對Http接口的測試。 一、開發(fā)接口測試案例的整體方案: 第一步:我們要分析出測試需求,并

    2024年03月14日
    瀏覽(28)

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請作者喝杯咖啡吧~博客贊助

支付寶掃一掃領取紅包,優(yōu)惠每天領

二維碼1

領取紅包

二維碼2

領紅包