【译】Rust标准库Trait指南(五)

629 阅读14分钟

原文标题:Tour of Rust's Standard Library Traits
原文链接:github.com/pretzelhamm…
公众号: Rust 碎碎念
翻译 by: Praying

内容目录 (译注:✅ 表示本文已翻译 ⏰ 表示后续翻译)

  • 引言 ✅
  • Trait 基础 ✅
  • 自动 Trait✅
  • 泛型 Trait✅
  • 格式化 Trait✅
  • 操作符 Trait⏰=>✅
  • 转换 Trait⏰
  • 错误处理 ⏰
  • 迭代器 Trait⏰
  • I/O Trait⏰
  • 总结 ⏰

算术 Trait(Arithmetic Traits)

Trait(s)分类(Category)操作符(Operator(s))描述(Description)
Add算术+相加
AddAssign算术+=相加并赋值
BitAnd算术&按位与
BitAndAssign算术&=按位与并赋值
BitXor算术^按位异或
BitXorAssign算术^=按位异或并赋值
Div算术/
DivAssign算术/=除并赋值
Mul算术*
MulAssign算术*=乘并赋值
Neg算术-一元求反
Not算术!一元逻辑求反
Rem算术%求余
RemAssign算术%=求余并赋值
Shl算术<<左移
ShlAssign算术<<=左移并赋值
Shr算术>>右移
ShrAssign算术>>=右移并赋值
Sub算术-
SubAssign算术-=减并赋值

我们没有必要把所有的算术操作符都仔细看一遍,毕竟它们中大多数都只作用于数值类型。我们将会讨论AddAddAssign,因为+操作符经常被重载用来完成其他事情,比如往集合里添加一项,或者进行拼接操作,这样我们就可以从最有趣的地方入手而不会重复。

Add & AddAssign

trait Add<Rhs = Self> {
    type Output;
    fn add(self, rhs: Rhs) -> Self::Output;
}

Add<Rhs, Output = T>类型可以被加到Rhs类型上并产生一个T作为输出。

例如,在Point上实现Add<Point, Output = Point>:

#[derive(Clone, Copy)]
struct Point {
    x: i32,
    y: i32,
}

impl Add for Point {
    type Output = Point;
    fn add(self, rhs: Point) -> Point {
        Point {
            x: self.x + rhs.x,
            y: self.y + rhs.y,
        }
    }
}

fn main() {
    let p1 = Point { x: 1, y: 2 };
    let p2 = Point { x: 3, y: 4 };
    let p3 = p1 + p2;
    assert_eq!(p3.x, p1.x + p2.x); // ✅
    assert_eq!(p3.y, p1.y + p2.y); // ✅
}

但是,如果我们只有Point的引用,那该怎么办呢?我们还能把它们相加么?让我们试试:

fn main() {
    let p1 = Point { x: 1, y: 2 };
    let p2 = Point { x: 3, y: 4 };
    let p3 = &p1 + &p2; // ❌
}

显然不可以,编译器抛出下面的提示:

error[E0369]: cannot add `&Point` to `&Point`
  --> src/main.rs:50:25
   |
50 |     let p3: Point = &p1 + &p2;
   |                     --- ^ --- &Point
   |                     |
   |                     &Point
   |
   = note: an implementation of `std::ops::Add` might be missing for `&Point`

在 Rust 的类型系统中,对于某个类型TT&T&mut T都会被视作是完全不同的类型,这意味着我们必须分别为它们提供 trait 的实现。让我们为&Point实现Add

impl Add for &Point {
    type Output = Point;
    fn add(self, rhs: &Point) -> Point {
        Point {
            x: self.x + rhs.x,
            y: self.y + rhs.y,
        }
    }
}

fn main() {
    let p1 = Point { x: 1, y: 2 };
    let p2 = Point { x: 3, y: 4 };
    let p3 = &p1 + &p2; // ✅
    assert_eq!(p3.x, p1.x + p2.x); // ✅
    assert_eq!(p3.y, p1.y + p2.y); // ✅
}

尽管如此,但是仍然感觉有些地方不太对。我们针对Point&Point实现了两份Add,它们恰好目前还做了相同的事情,但是我们不能保证将来也是如此。例如,假设我们决定,当我们把两个Point相加时,我们想要创建一个包含这两个PointLine类型而不是创建一个新的Point,那么我们会把Add的实现更新:

use std::ops::Add;

#[derive(Copy, Clone)]
struct Point {
    x: i32,
    y: i32,
}

#[derive(Copy, Clone)]
struct Line {
    start: Point,
    end: Point,
}

// we updated this impl
impl Add for Point {
    type Output = Line;
    fn add(self, rhs: Point) -> Line {
        Line {
            start: self,
            end: rhs,
        }
    }
}

// but forgot to update this impl, uh oh!
impl Add for &Point {
    type Output = Point;
    fn add(self, rhs: &Point) -> Point {
        Point {
            x: self.x + rhs.x,
            y: self.y + rhs.y,
        }
    }
}

fn main() {
    let p1 = Point { x: 1, y: 2 };
    let p2 = Point { x: 3, y: 4 };
    let line: Line = p1 + p2; // ✅

    let p1 = Point { x: 1, y: 2 };
    let p2 = Point { x: 3, y: 4 };
    let line: Line = &p1 + &p2; // ❌ expected Line, found Point
}

我们当前针对&PointAdd实现就产生了一个不必要的维护负担,我们希望这个实现能够自动匹配Point的实现而无需我们每次在修改Point的实现时都手动维护更新。我们想要保持我们的代码尽可能地 DRY(Don't Repeat Yourself,不要重复自己)。幸运的是这是可以实现的:

// updated, DRY impl
impl Add for &Point {
    type Output = <Point as Add>::Output;
    fn add(self, rhs: &Point) -> Self::Output {
        Point::add(*self, *rhs)
    }
}

fn main() {
    let p1 = Point { x: 1, y: 2 };
    let p2 = Point { x: 3, y: 4 };
    let line: Line = p1 + p2; // ✅

    let p1 = Point { x: 1, y: 2 };
    let p2 = Point { x: 3, y: 4 };
    let line: Line = &p1 + &p2; // ✅
}

AddAssign<Rhs>类型能够让我们和Rhs类型相加并赋值。该 trait 声明如下:

trait AddAssign<Rhs = Self> {
    fn add_assign(&mut self, rhs: Rhs);
}

Point&Point为例:

use std::ops::AddAssign;

#[derive(Copy, Clone)]
struct Point {
    x: i32,
    y: i32
}

impl AddAssign for Point {
    fn add_assign(&mut self, rhs: Point) {
        self.x += rhs.x;
        self.y += rhs.y;
    }
}

impl AddAssign<&Point> for Point {
    fn add_assign(&mut self, rhs: &Point) {
        Point::add_assign(self, *rhs);
    }
}

fn main() {
    let mut p1 = Point { x: 1, y: 2 };
    let p2 = Point { x: 3, y: 4 };
    p1 += &p2;
    p1 += p2;
    assert!(p1.x == 7 && p1.y == 10);
}

闭包 Trait(Closure Traits)

Trait(s)分类(Category)操作符(Operator(s))描述(Description)
Fn闭包(...args)不可变闭包调用
FnMut闭包(...args)可变闭包调用
FnOnce闭包(...args)一次性闭包调用

FnOnce, FnMut, & Fn

trait FnOnce<Args> {
    type Output;
    fn call_once(self, args: Args) -> Self::Output;
}

trait FnMut<Args>: FnOnce<Args> {
    fn call_mut(&mut self, args: Args) -> Self::Output;
}

trait Fn<Args>: FnMut<Args> {
    fn call(&self, args: Args) -> Self::Output;
}

虽然存在这些 trait,但是在 stable 的 Rust 中,我们无法为自己的类型实现这些 trait。我们能够创建的唯一能够实现这些 trait 的类型就是闭包。闭包根据其从环境中所捕获的内容来决定它到底是实现FnOnceFnMut还是Fn

FnOnce闭包只能被调用一次,因为它会在执行过程中消耗掉某些值:

fn main() {
    let range = 0..10;
    let get_range_count = || range.count();
    assert_eq!(get_range_count(), 10); // ✅
    get_range_count(); // ❌
}

迭代器上的.count()方法会消耗迭代器,因此它只能被调用一次。因此,我们的闭包也只能调用一次。这也是为什么我们在尝试调用第二次的时候会得到下面的错误:

error[E0382]: use of moved value: `get_range_count`
 --> src/main.rs:5:5
  |
4 |     assert_eq!(get_range_count(), 10);
  |                ----------------- `get_range_count` moved due to this call
5 |     get_range_count();
  |     ^^^^^^^^^^^^^^^ value used here after move
  |
note: closure cannot be invoked more than once because it moves the variable `range` out of its environment
 --> src/main.rs:3:30
  |
3 |     let get_range_count = || range.count();
  |                              ^^^^^
note: this value implements `FnOnce`, which causes it to be moved when called
 --> src/main.rs:4:16
  |
4 |     assert_eq!(get_range_count(), 10);
  |                ^^^^^^^^^^^^^^^

FnMut闭包可以被多次调用,并且可以修改它从环境中捕获到的变量。我们可以说FnMut有副作用或者是有状态的(stateful)。下面是一个闭包的示例,通过从迭代器中追踪它见到的最小值来过滤所有非升序的值。

fn main() {
    let nums = vec![0, 4, 2, 8, 10, 7, 15, 18, 13];
    let mut min = i32::MIN;
    let ascending = nums.into_iter().filter(|&n| {
        if n <= min {
            false
        } else {
            min = n;
            true
        }
    }).collect::<Vec<_>>();
    assert_eq!(vec![0, 4, 8, 10, 15, 18], ascending); // ✅
}

FnOnce会获取它的参数的所有权并且只能被调用一次,但是FnMut仅要求获取参数的可变引用并且可以被多次调用,从这一点上来讲,FnMut细化了FnOnceFnMut可以被用于任何可以使用FnOnce的地方。

Fn闭包也可以被调用多次,但是它不能修改从环境中捕获的变量。我们可以说,Fn闭包没有副作用或者无状态的(stateless)。下面是一个示例,从一个迭代器中过滤出所有小于某个栈上变量的数字,该变量是它是环境中捕获到的:

fn main() {
    let nums = vec![0, 4, 2, 8, 10, 7, 15, 18, 13];
    let min = 9;
    let greater_than_9 = nums.into_iter().filter(|&n| n > min).collect::<Vec<_>>();
    assert_eq!(vec![10, 15, 18, 13], greater_than_9); // ✅
}

FnMut要求可变引用并且可以被多次调用,Fn只要求不可变引用并可以被多次调用,从这一点来讲,Fn细化了FnMutFn可以被用于任何可以使用FnMut的地方,当然也包括可以使用FnOnce的地方。

如果一个闭包不从环境中捕获任何变量,从技术角度来讲它算不上是闭包,而只是一个被匿名声明的内联函数,并且可以作为一个普通函数指针(即Fn)被使用和传递,这包括可以使用FnMutFnOnce的地方。

fn add_one(x: i32) -> i32 {
    x + 1
}

fn main() {
    let mut fn_ptr: fn(i32) -> i32 = add_one;
    assert_eq!(fn_ptr(1), 2); // ✅

    // capture-less closure cast to fn pointer
    fn_ptr = |x| x + 1; // same as add_one
    assert_eq!(fn_ptr(1), 2); // ✅
}

下面是一个传递普通函数指针而不是闭包的示例:

fn main() {
    let nums = vec![-1, 1, -2, 2, -3, 3];
    let absolutes: Vec<i32> = nums.into_iter().map(i32::abs).collect();
    assert_eq!(vec![1, 1, 2, 2, 3, 3], absolutes); // ✅
}

其他 Trait (Other Traits)

Trait(s)分类(Category)操作符(Operator(s))描述(Description)
Deref其他*不可变解引用
DerefMut其他*可变解引用
Drop其他-类型析构
Index其他[]不可变索引
IndexMut其他[]可变索引
RangeBounds其他..区间
trait Deref {
    type Target: ?Sized;
    fn deref(&self) -> &Self::Target;
}

trait DerefMut: Deref {
    fn deref_mut(&mut self) -> &mut Self::Target;
}

Deref<Target = T>类型可以使用*操作符解引用为T类型。这在像BoxRc这样的智能指针类型中有很明显的用例。尽管如此,但是我们在 Rust 代码中很少见到这种显式的解引用操作,这是因为 Rust 有一个被称为解引用强制转换(deref coercion)的特性。

当类型被作为函数参数传递、从函数返回或者作为方法调用的一部分时,Rust 会自动对这些类型进行解引用。这也解释了为什么我们可以在一个期望&str&[T]的函数中可以传入&String&Vec<T>,因为String实现了Deref<Target = str>并且Vec<T>实现了Deref<Target = [T]>

DerefDerefMut应该仅被实现于智能指针类型。人们误用和滥用这些 trait 的最常见的方式是,试图把 OOP(面向对象程序设计)风格的数据继承塞进 Rust 中。这样是行不通的。Rust 不是 OOP。让我们进行一些测试,来看看它是在哪里、怎么样以及为什么行不通。让我们从下面的例子开始:

use std::ops::Deref;

struct Human {
    health_points: u32,
}

enum Weapon {
    Spear,
    Axe,
    Sword,
}

// a Soldier is just a Human with a Weapon
struct Soldier {
    human: Human,
    weapon: Weapon,
}

impl Deref for Soldier {
    type Target = Human;
    fn deref(&self) -> &Human {
        &self.human
    }
}

enum Mount {
    Horse,
    Donkey,
    Cow,
}

// a Knight is just a Soldier with a Mount
struct Knight {
    soldier: Soldier,
    mount: Mount,
}

impl Deref for Knight {
    type Target = Soldier;
    fn deref(&self) -> &Soldier {
        &self.soldier
    }
}

enum Spell {
    MagicMissile,
    FireBolt,
    ThornWhip,
}

// a Mage is just a Human who can cast Spells
struct Mage {
    human: Human,
    spells: Vec<Spell>,
}

impl Deref for Mage {
    type Target = Human;
    fn deref(&self) -> &Human {
        &self.human
    }
}

enum Staff {
    Wooden,
    Metallic,
    Plastic,
}

// a Wizard is just a Mage with a Staff
struct Wizard {
    mage: Mage,
    staff: Staff,
}

impl Deref for Wizard {
    type Target = Mage;
    fn deref(&self) -> &Mage {
        &self.mage
    }
}

fn borrows_human(human: &Human) {}
fn borrows_soldier(soldier: &Soldier) {}
fn borrows_knight(knight: &Knight) {}
fn borrows_mage(mage: &Mage) {}
fn borrows_wizard(wizard: &Wizard) {}

fn example(human: Human, soldier: Soldier, knight: Knight, mage: Mage, wizard: Wizard) {
    // all types can be used as Humans
    borrows_human(&human);
    borrows_human(&soldier);
    borrows_human(&knight);
    borrows_human(&mage);
    borrows_human(&wizard);
    // Knights can be used as Soldiers
    borrows_soldier(&soldier);
    borrows_soldier(&knight);
    // Wizards can be used as Mages
    borrows_mage(&mage);
    borrows_mage(&wizard);
    // Knights & Wizards passed as themselves
    borrows_knight(&knight);
    borrows_wizard(&wizard);
}

乍看之下,上面的代码似乎还不错!但是,仔细观察之后它就没这么好了。首先,解引用强制转换仅作用于引用,因此,当我们想要传递所有权的时候它是行不通的:

fn takes_human(human: Human) {}

fn example(human: Human, soldier: Soldier, knight: Knight, mage: Mage, wizard: Wizard) {
    // all types CANNOT be used as Humans
    takes_human(human);
    takes_human(soldier); // ❌
    takes_human(knight); // ❌
    takes_human(mage); // ❌
    takes_human(wizard); // ❌
}

此外,解引用强制转换在泛型上下文中是无法工作的。假定我们仅在 humans 上实现某个 trait:

trait Rest {
    fn rest(&self);
}

impl Rest for Human {
    fn rest(&self) {}
}

fn take_rest<T: Rest>(rester: &T) {
    rester.rest()
}

fn example(human: Human, soldier: Soldier, knight: Knight, mage: Mage, wizard: Wizard) {
    // all types CANNOT be used as Rest types, only Human
    take_rest(&human);
    take_rest(&soldier); // ❌
    take_rest(&knight); // ❌
    take_rest(&mage); // ❌
    take_rest(&wizard); // ❌
}

而且,尽管解引用强制转换在很多场景都可以使用,但它不是万能的。它无法作用于操作数,尽管操作符只是方法调用的语法糖。假定,我们想要Mage(魔术师)通过+=操作符学会Spell(拼写)

impl DerefMut for Wizard {
    fn deref_mut(&mut self) -> &mut Mage {
        &mut self.mage
    }
}

impl AddAssign<Spell> for Mage {
    fn add_assign(&mut self, spell: Spell) {
        self.spells.push(spell);
    }
}

fn example(mut mage: Mage, mut wizard: Wizard, spell: Spell) {
    mage += spell;
    wizard += spell; // ❌ wizard not coerced to mage here
    wizard.add_assign(spell); // oof, we have to call it like this 🤦
}

在具有 OOP 风格的数据继承的编程语言中,一个方法中的self的值总是等于调用这个方法的类型,但是在 Rust 中,self的值永远等于实现这个方法的类型:

struct Human {
    profession: &'static str,
    health_points: u32,
}

impl Human {
    // self will always be a Human here, even if we call it on a Soldier
    fn state_profession(&self) {
        println!("I'm a {}!", self.profession);
    }
}

struct Soldier {
    profession: &'static str,
    human: Human,
    weapon: Weapon,
}

fn example(soldier: &Soldier) {
    assert_eq!("servant", soldier.human.profession);
    assert_eq!("spearman", soldier.profession);
    soldier.human.state_profession(); // prints "I'm a servant!"
    soldier.state_profession(); // still prints "I'm a servant!" 🤦
}

当在一个新类型上实现DerefDerefMut时,上面的陷阱令人震惊。假定我们想要创建一个SortedVec类型,它就是一个Vec只不过是有序的。下面是我们可能的实现方式:

struct SortedVec<T: Ord>(Vec<T>);

impl<T: Ord> SortedVec<T> {
    fn new(mut vec: Vec<T>) -> Self {
        vec.sort();
        SortedVec(vec)
    }
    fn push(&mut self, t: T) {
        self.0.push(t);
        self.0.sort();
    }
}

显然,这里我们不能实现DerefMut<Target = Vec<T>>,否则任何使用SortedVec的人都能轻易打破已排好的顺序。但是,实现Deref<Target = Vec<T>>就一定安全么?试试找出下面程序中的 bug:

use std::ops::Deref;

struct SortedVec<T: Ord>(Vec<T>);

impl<T: Ord> SortedVec<T> {
    fn new(mut vec: Vec<T>) -> Self {
        vec.sort();
        SortedVec(vec)
    }
    fn push(&mut self, t: T) {
        self.0.push(t);
        self.0.sort();
    }
}

impl<T: Ord> Deref for SortedVec<T> {
    type Target = Vec<T>;
    fn deref(&self) -> &Vec<T> {
        &self.0
    }
}

fn main() {
    let sorted = SortedVec::new(vec![2, 8, 6, 3]);
    sorted.push(1);
    let sortedClone = sorted.clone();
    sortedClone.push(4);
}

我们未曾给SortedVec实现Clone,所以当我们调用.clone()方法时,编译器使用解引用强制转换把它解析为Vec上的方法调用,所以它会返回一个Vec而不是一个SortedVec

fn main() {
    let sorted: SortedVec<i32> = SortedVec::new(vec![2, 8, 6, 3]);
    sorted.push(1); // still sorted

    // calling clone on SortedVec actually returns a Vec 🤦
    let sortedClone: Vec<i32> = sorted.clone();
    sortedClone.push(4); // sortedClone no longer sorted 💀
}

不管怎样,上面的限制、约束或者陷阱都不是 Rust 的错,因为 Rust 从来都没有被设计成一门 OO(面向对象)的语言或者把支持 OOP(面向对象程序设计)模式放在首位。

本节的要点在于不要试图在DerefDerefMut的实现耍小聪明。它们仅仅适用于智能指针类型,目前只能在标准库中实现,因为智能指针类型目前需要 unstable 的特性和编译器的魔法才能工作。如果我们想要类似于DerefDerefMut的功能和行为,我们可以去了解一下后面会提到的AsRefAsMut

Index & IndexMut

trait Index<Idx: ?Sized> {
    type Output: ?Sized;
    fn index(&self, index: Idx) -> &Self::Output;
}

trait IndexMut<Idx>: Index<Idx> where Idx: ?Sized {
    fn index_mut(&mut self, index: Idx) -> &mut Self::Output;
}

我们可以将[]索引到带有 T 值的Index<T, Output = U>类型,索引操作将返回&U值。为了语法方便,编译器会自动在索引操作返回值的前面插入一个解引用操作符*

fn main() {
    // Vec<i32> impls Index<usize, Output = i32> so
    // indexing Vec<i32> should produce &i32s and yet...
    let vec = vec![1, 2, 3, 4, 5];
    let num_ref: &i32 = vec[0]; // ❌ expected &i32 found i32

    // above line actually desugars to
    let num_ref: &i32 = *vec[0]; // ❌ expected &i32 found i32

    // both of these alternatives work
    let num: i32 = vec[0]; // ✅
    let num_ref = &vec[0]; // ✅
}

为了展示我们自己如何实现Index,下面是一个有趣的示例,这个例子展示了我们如何使用一个新类型和Indextrait 在Vec上实现环绕索引和非负索引:

use std::ops::Index;

struct WrappingIndex<T>(Vec<T>);

impl<T> Index<usize> for WrappingIndex<T> {
    type Output = T;
    fn index(&self, index: usize) -> &T {
        &self.0[index % self.0.len()]
    }
}

impl<T> Index<i128> for WrappingIndex<T> {
    type Output = T;
    fn index(&self, index: i128) -> &T {
        let self_len = self.0.len() as i128;
        let idx = (((index % self_len) + self_len) % self_len) as usize;
        &self.0[idx]
    }
}

#[test] // ✅
fn indexes() {
    let wrapping_vec = WrappingIndex(vec![1, 2, 3]);
    assert_eq!(1, wrapping_vec[0_usize]);
    assert_eq!(2, wrapping_vec[1_usize]);
    assert_eq!(3, wrapping_vec[2_usize]);
}

#[test] // ✅
fn wrapping_indexes() {
    let wrapping_vec = WrappingIndex(vec![1, 2, 3]);
    assert_eq!(1, wrapping_vec[3_usize]);
    assert_eq!(2, wrapping_vec[4_usize]);
    assert_eq!(3, wrapping_vec[5_usize]);
}

#[test] // ✅
fn neg_indexes() {
    let wrapping_vec = WrappingIndex(vec![1, 2, 3]);
    assert_eq!(1, wrapping_vec[-3_i128]);
    assert_eq!(2, wrapping_vec[-2_i128]);
    assert_eq!(3, wrapping_vec[-1_i128]);
}

#[test] // ✅
fn wrapping_neg_indexes() {
    let wrapping_vec = WrappingIndex(vec![1, 2, 3]);
    assert_eq!(1, wrapping_vec[-6_i128]);
    assert_eq!(2, wrapping_vec[-5_i128]);
    assert_eq!(3, wrapping_vec[-4_i128]);
}

这里没有要求Idx类型是数值类型或者是一个Range,它也可以是一个枚举!下面是一个使用篮球位置在一支球队里检索球员的例子:

use std::ops::Index;

enum BasketballPosition {
    PointGuard,
    ShootingGuard,
    Center,
    PowerForward,
    SmallForward,
}

struct BasketballPlayer {
    name: &'static str,
    position: BasketballPosition,
}

struct BasketballTeam {
    point_guard: BasketballPlayer,
    shooting_guard: BasketballPlayer,
    center: BasketballPlayer,
    power_forward: BasketballPlayer,
    small_forward: BasketballPlayer,
}

impl Index<BasketballPosition> for BasketballTeam {
    type Output = BasketballPlayer;
    fn index(&self, position: BasketballPosition) -> &BasketballPlayer {
        match position {
            BasketballPosition::PointGuard => &self.point_guard,
            BasketballPosition::ShootingGuard => &self.shooting_guard,
            BasketballPosition::Center => &self.center,
            BasketballPosition::PowerForward => &self.power_forward,
            BasketballPosition::SmallForward => &self.small_forward,
        }
    }
}

Drop

trait Drop {
    fn drop(&mut self);
}

如果一个类型实现了Drop,那么drop将会在该类型离开作用域但是销毁之前被调用。我们很少需要去为我们的类型实现它,但是如果一个类型中持有某些外部资源,这些资源需要在类型销毁时被清理,这种情况下就会用到了。

标准库中有一个BufWriter类型让我们能够把写入的数据缓冲到Write类型中。但是,如果BufWriter在它里面的内容被刷入到底层的Write类型之前就被销毁了,该怎么办呢?幸运的是那是不可能的!BufWriter实现了Droptrait,因此,无论什么它什么时候离开作用域,flush总会被调用!

impl<W: Write> Drop for BufWriter<W> {
    fn drop(&mut self) {
        self.flush_buf();
    }
}

此外,Rust 中的Mutexs没有unlock()方法,因为它们不需要!在Mutex上调用lock()会返回一个MutexGuard,当MutexGuard离开作用域时,它会自动解锁(unlock)Mutex,这要归功于它的Drop实现:

impl<T: ?Sized> Drop for MutexGuard<'_, T> {
    fn drop(&mut self) {
        unsafe {
            self.lock.inner.raw_unlock();
        }
    }
}

一般而言,如果你正在实现对某类资源的抽象,这类资源需要在使用后被清理,那就是时候充分利用Drop trait 了。