跳转至

Trait

1 Default

  • 功能:提供默认值
  • 源码
pub trait Default: Sized {
    fn default() -> Self;
}

Sometimes, you want to fall back to some kind of default value, and don't particularly care what it is. This comes up often with struct that define a set of options:

```rust

[allow(dead_code)]

struct SomeOptions { foo: i32, bar: f32, }

How can we define some default values? You can use `Default`:


```rust
#[allow(dead_code)]
#[derive(Default)]
struct SomeOptions {
    foo: i32,
    bar: f32,
}

fn main() {
    let options: SomeOptions = Default::default();
}

Now, you get all of the default values. Rust implements Default for various primitives types.

If you want to override a particular option, but still retain the other defaults:

#[allow(dead_code)]
#[derive(Default)]
struct SomeOptions {
    foo: i32,
    bar: f32,
}
fn main() {
    let options = SomeOptions { foo: 42, ..Default::default() };
}

1.1 派生

This trait can be used with #[derive] if all of the type's fields implement Default. When derived, it will use the default value for each field's type.

1.1.1 enum

When using #[derive(Default)] on an enum, you need to choose which unit variant will be default. You do this by placing the #[default] attribute on the variant.

#[derive(Default)]
enum Kind {
    #[default]
    A,
    B,
    C,
}

You cannot use the #[default] attribute on non-unit or non-exhaustive variants.

1.2 实现

Provide an implementation for the default() method that returns the value of your type that should be the default:

#![allow(dead_code)]
enum Kind {
    A,
    B,
    C,
}

impl Default for Kind {
    fn default() -> Self { Kind::A }
}
#[allow(dead_code)]
#[derive(Default)]
struct SomeOptions {
    foo: i32,
    bar: f32,
}

2 Deref

功能:用于不可变解引用操作,例如 *v。在可变上下文中,使用 DerefMutDeref 和 DerefMut 的规则是专门为容纳智能指针而设计的。因此,Deref 只应为智能指针实现,以避免混淆。

实现:

pub trait Deref {
    type Target: ?Sized;

    // Required method
    fn deref(&self) -> &Self::Target;
}

2.1 解引用规则

如果 T 实现 Deref<Target = U>,并且 x 是 T 类型的值,则: - 在不可变的上下文中,*x (其中 T 既不是引用也不是裸指针) 等效于 *Deref::deref(&x)。 - &T 类型的值被强制为 &U 类型的值 - T 隐式地实现了 U 类型的所有 (immutable) 方法。

2.2 函数和方法的隐式解引用强制转换

use std::ops::Deref;

impl<T> Deref for MyBox<T> {
    type Target = T;

    fn deref(&self) -> &T {
        &self.0
    }
}
fn hello(name: &str) {
    println!("Hello, {}!", name);
}
fn main() {
    let m = MyBox::new(String::from("Rust"));
    hello(&m);
}

上面能从 MyBox 类型解引用到 &str 类型,经过了多次解引用,步骤是 &m->&String->&str,其中 String 类型也实现了 Deref 特征:

#[stable(feature = "rust1", since = "1.0.0")]
impl ops::Deref for String {
    type Target = str;

    #[inline]
    fn deref(&self) -> &str {
        unsafe { str::from_utf8_unchecked(&self.vec) }
    }
}

如果没有解引用强制转换能力,那么我们不得不写如下代码:

fn main() {
    let m = MyBox::new(String::from("Rust"));
    hello(&(*m)[..]);
}

(*m) 将 MyBox<String> 解引用为 String。接着 & 和 [..] 获取了整个 String 的字符串 slice 来匹配 hello 的签名。没有解引用强制转换所有这些符号混在一起将更难以读写和理解。解引用强制转换使得 Rust 自动的帮我们处理这些转换。

2.3 解引用强制转换如何与可变性交互

类似于使用 Deref trait 重载不可变引用的 * 运算符,Rust 提供了 DerefMut trait 用于重载可变引用的 * 运算符。

Rust 在发现类型和 trait 的实现满足以下三种情况时会进行解引用强制转换: - 当 T: Deref<Target=U> :从 &T 到 &U。 - 当 T: DerefMut<Target=U> :从 &mut T 到 &mut U。 - 当 T: Deref<Target=U> :从 &mut T 到 &U

3 Drop

Drop 特征相当于 C++ 中的析构函数。

pub trait Drop {
    // Required method
    fn drop(&mut self);
}

此析构函数由两个组件组成: - 如果为此类型实现了特殊的 Drop trait,则对该值调用 Drop::drop。 - 自动生成的 “drop glue” 递归调用该值的所有字段的析构函数。

通过调用 Drop的参数实现来实现。 实现 Copy 的整数和其他类型不受 drop 的影响。 这个功能并不神奇。它的字面定义为

pub fn drop<T>(_x: T) { }

由于 _x 已移入函数,因此它会在函数返回之前自动丢弃。

例如 integers. 这样的值被复制并将 then 移到函数中,因此该值在此函数调用之后仍然存在。

#[derive(Copy, Clone)]
struct Foo(u8);

let x = 1;
let y = Foo(2);
drop(x); // `x` 的副本已移动并丢弃
drop(y); // `y` 的副本已移动并丢弃

println!("x: {}, y: {}", x, y.0); // 仍然可用

由于 Rust 自动调用所有包含字段的析构函数,因此在大多数情况下,您无需实现 Drop。但是在某些情况下它很有用,例如对于直接管理资源的类型。该资源可能是内存,可能是文件描述符,可能是网络套接字。一旦不再使用该类型的值,则应通过释放内存或关闭文件或套接字 “clean up” 资源。这是析构函数的工作,因此也是 Drop::drop 的工作。

struct HasDrop;

impl Drop for HasDrop {
    fn drop(&mut self) {
        println!("Dropping HasDrop!");
    }
}

struct HasTwoDrops {
    one: HasDrop,
    two: HasDrop,
}

impl Drop for HasTwoDrops {
    fn drop(&mut self) {
        println!("Dropping HasTwoDrops!");
    }
}

fn main() {
    let _x = HasTwoDrops { one: HasDrop, two: HasDrop };
    println!("Running!");
}

Rust 将首先为 _x 调用 Drop::drop,然后为 _x.one 和 _x.two 调用,这意味着运行此命令将打印

Running!
Dropping HasTwoDrops!
Dropping HasDrop!
Dropping HasDrop!

即使我们删除了针对 HasTwoDrop 的 Drop 的实现,其字段的析构函数仍然会被调用。 这将导致

Running!
Dropping HasDrop!
Dropping HasDrop!

3.1 Drop 指令执行顺序

  • 对于结构体,其声明顺序相同。
  • 对于局部变量,以相反的顺序丢弃。
struct Foo;

impl Drop for Foo {
    fn drop(&mut self) {
        println!("Dropping Foo!")
    }
}

struct Bar;

impl Drop for Bar {
    fn drop(&mut self) {
        println!("Dropping Bar!")
    }
}

fn main() {
    let _foo = Foo;
    let _bar = Bar;
}

输出:

Dropping Bar!
Dropping Foo!

3.2 注意事项

  • Rust 通过不允许您直接调用 Drop::drop 来防止误用,会出现编译器错误。如果您想显式调用一个值的析构函数,可以使用 mem::drop 代替。
struct Droppable {
    name: &'static str,
}

// 这个简单的 `drop` 实现添加了打印到控制台的功能。
impl Drop for Droppable {
    fn drop(&mut self) {
        println!("> Dropping {}", self.name);
    }
}

fn main() {
    let _a = Droppable { name: "a" };

    // 代码块 A
    {
        let _b = Droppable { name: "b" };

        // 代码块 B
        {
            let _c = Droppable { name: "c" };
            let _d = Droppable { name: "d" };

            println!("Exiting block B");
        }
        println!("Just exited block B");

        println!("Exiting block A");
    }
    println!("Just exited block A");

    // 变量可以手动使用 `drop` 函数来销毁。
    drop(_a);
    // 试一试 ^ 将此行注释掉。

    println!("end of the main function");

    // `_a` *不会*在这里再次销毁,因为它已经被(手动)销毁。
}
  • 您不能在同一类型上同时实现 Copy 和 DropCopy 类型被编译器隐式复制,这使得很难预测何时以及将执行析构函数的频率。因此,这些类型不能有析构函数。

4 Copy

https://www.rustwiki.org.cn/zh-CN/core/marker/trait.Copy.html 用于 隐式复制 对象。是按位复制的,相当于 C++ 中 浅复制,如果有指针,则只是复制了指针值,而指针指向的堆内存没有复制。

默认情况下,变量绑定具有移动语义。换句话说:

#[derive(Debug)]
struct Foo;

let x = Foo;

let y = x;

// `x` 已移入 `y`,因此无法使用

// println!("{x:?}"); // error: use of moved value

但是,如果类型实现 Copy,则它具有复制语义:

// 我们可以派生一个 `Copy` 实现。
// `Clone` 也是必需的,因为它是 `Copy` 的 super trait。
#[derive(Debug, Copy, Clone)]
struct Foo;

let x = Foo;

let y = x;

// `y` 是 `x` 的副本

println!("{x:?}"); // A-OK!

重要的是要注意,在这两个示例中,唯一的区别是分配后是否允许您访问 x。在后台,复制和移动都可能导致将位复制到内存中,尽管有时会对其进行优化。

4.1 如何实现 Copy

有两种方法可以在您的类型上实现 Copy。最简单的是使用 derive

#[derive(Copy, Clone)]
struct MyStruct;

您还可以手动实现 Copy 和 Clone

struct MyStruct;

impl Copy for MyStruct { }

impl Clone for MyStruct {
    fn clone(&self) -> MyStruct {
        *self
    }
}

这两者之间有一个小小的区别: derive 策略还将 Copy 绑定在类型参数上,这并不总是需要的。

4.2 4.2Copy 和 Clone 有什么区别?

  • Copy 是隐式发生的,例如作为赋值 y = x 的一部分。Copy 的行为不可重载;它始终是简单的按位复制。
  • Clone 是一个显式操作,x.clone()Clone 的实现可以提供安全复制值所需的任何特定于类型的行为。例如,用于 String 的实现需要在堆中复制指向字符串的缓冲区。 String 值的简单按位副本将仅复制指针,从而导致该行向下双重释放。因此,String 是 Clone,但不是 Copy
  • Clone 是 Copy 的一个 super trait,所以所有 Copy 的东西也必须实现 Clone。如果类型为 Copy,则其 Clone 实现仅需要返回 *self (请参见上面的示例)。

4.3 什么时候可以输入 Copy

如果类型的所有组件都实现 Copy,则它可以实现 Copy。例如,此结构体可以是 Copy

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

一个结构体可以是 Copy,而 i32 是 Copy,因此 Point 有资格成为 Copy。相比之下,考虑

struct PointList {
    points: Vec<Point>,
}

结构体 PointList 无法实现 Copy,因为 Vec<T>不是Copy。如果尝试派生Copy` 实现,则会收到错误消息:

the trait `Copy` cannot be implemented for this type; field `points` does not implement `Copy`

共享引用 (&T) 也是 Copy,因此,即使类型中包含不是 Copy 类型的共享引用 T,也可以是 Copy。 考虑下面的结构体,它可以实现 Copy,因为它从上方仅对我们的非 Copy 类型 PointList 持有一个 shared 引用

#[derive(Copy, Clone)]
struct PointListWrapper<'a> {
    point_list_ref: &'a PointList,
}

4.4 什么时候我的类型不能为 Copy

某些类型无法安全复制。例如,复制 &mut T 将创建一个别名可变引用。 复制 String 将重复管理 String 缓冲区,从而导致双重释放。

概括后一种情况,任何实现 Drop 的类型都不能是 Copy,因为它除了管理自己的 size_of::<T> 字节外还管理一些资源。

5 Clone

https://www.rustwiki.org.cn/zh-CN/core/clone/index.html 用于 显式复制 对象。可以是 浅复制 的,也可以是 深复制,但正确的实现当然是深复制。 为了强制执行这些特性,Rust 不允许您重新实现 Copy,但是您可以重新实现 Clone 并运行任意代码。

5.1 如何实现 Clone

Copy 类型应该实现 Clone 的简单实现。更正式地: 如果 T: Copyx: T 和 y: &T,则 let x = y.clone(); 等效于 let x = *y;。 手动执行时应注意保持不变。但是,不安全的代码一定不能依靠它来确保内存安全。

一个示例是持有函数指针的泛型结构体。在这种情况下,不能对 Clone 的实现进行派生操作,而可以将其实现为:

struct Generate<T>(fn() -> T);

impl<T> Copy for Generate<T> {}

impl<T> Clone for Generate<T> {
    fn clone(&self) -> Self {
        *self
    }
}

5.2 常用方法

5.2.1 fn clone(&self) -> Self

let hello = "Hello"; // &str 实现克隆

assert_eq!("Hello", hello.clone());

5.2.2 fn clone_from(&mut self, source: &Self)

从 source 执行复制分配。

a.clone_from(&b) 在功能上等同于 a = b.clone(),但可以被覆盖以重用 a 的资源以避免不必要的分配。

6 Sized

https://course.rs/advance/into-types/sized.html

如果从编译器何时能获知类型大小的角度出发,类型可以分成两类:

  • 定长类型 ( sized ),这些类型的大小在编译时是已知的
  • 不定长类型 ( unsized ),与定长类型相反,它的大小只有到了程序运行时才能动态获知,这种类型又被称之为 DST 集合 VecString 和 HashMap 等,这些底层数据只是保存在堆上,在栈中还存有一个引用类型,该引用包含了集合的内存地址、元素数目、分配空间信息,通过这些信息,编译器对于该集合的实际大小了若指掌,最最重要的是:栈上的引用类型是固定大小的,因此它们依然是固定大小的类型。

Rust 中常见的 DST 类型有: str[T]dyn Trait它们都无法单独被使用,必须要通过引用或者 Box 来间接使用 。str,它既不是 String 动态字符串,也不是 &str 字符串切片,而是一个 str。它是一个动态类型,同时还是 String 和 &str 的底层数据类型。

fn generic<T: Sized>(t: T) {
    // --snip--
}

Rust 自动添加的特征约束 T: Sized,表示泛型函数只能用于一切实现了 Sized 特征的类型上,而 所有在编译时就能知道其大小的类型,都会自动实现 Sized 特征

fn generic<T: ?Sized>(t: &T) {
    // --snip--
}

?Sized 特征用于表明类型 T 既有 可能是固定大小的类型,也可能是动态大小的类型。还有一点要注意的是,函数参数类型从 T 变成了 &T,因为 T 可能是动态大小的,因此需要用一个固定大小的指针 (引用) 来包裹它。

7 Send & Sync

  • https://rustwiki.org/zh-CN/book/ch16-04-extensible-concurrency-sync-and-send.html

  • Sync 标记 trait 表明一个实现了 Sync 的类型可以安全的在多个线程中拥有其值的引用。

  • Send 标记 trait 表明类型的所有权可以在线程间传递。

通常并不需要手动实现 Send 和 Sync trait,因为由 Send 和 Sync 的类型组成的类型,自动就是 Send 和 Sync 的。因为他们是标记 trait,甚至都不需要实现任何方法。他们只是用来加强并发相关的不可变性的。

7.1 Send

  • https://www.rustwiki.org.cn/zh-CN/std/marker/trait.Send.html

任何完全由 Send 的类型组成的类型也会自动被标记为 Send。几乎所有基本类型都是 Send 的,非 Send 类型的一个例子是引用计数指针 rc::Rc和裸指针(raw pointer)。如果两个线程试图克隆指向相同引用计数值的 Rc,它们可能会同时尝试更新引用计数,这是 未定义行为 因为 Rc 不使用原子操作。

它的表亲 sync::Arc 确实使用原子操作 (产生一些开销),因此它是 Send

7.2 自定义 Send 特征类

// 引入 std::marker 中的 Send 特征
use std::marker::Send;

// 自定义结构体,实现 Send 特征
struct MyStruct;

// 实现 Send 特征
unsafe impl Send for MyStruct {}

fn main() {
    // 在多线程环境中创建 MyStruct 类型的实例
    std::thread::spawn(|| {
        let my_instance = MyStruct;
        // 在这里使用 my_instance
    });

    // 继续在主线程中执行其他操作
}
在上面的示例中,MyStruct 结构体实现了 Send 特征。注意,为了实现 Send 特征,需要使用 unsafe 块。这是因为 Rust 信任开发者确保类型的安全性。

需要注意的是,大多数类型都已经实现了 Send 特征,因为它们通常是可以安全地在线程之间传递的。只有在你有自定义类型或涉及裸指针等情况时,你可能需要手动实现 Send 特征。

7.3 Sync

  • https://www.rustwiki.org.cn/zh-CN/std/marker/trait.Sync.html

8 Fn

在 Rust 中,FnFnMutFnOnce 是 trait,用于表示不同类型的函数调用。这些 trait 用于指定一个函数对象(function object)可以被以不同方式调用。 其中 FnOnce 是 FnMut 的超特征,FnMut 又是 Fn 的超特征

8.1 Fn

https://www.rustwiki.org.cn/zh-CN/std/ops/trait.Fn.html - 功能:采用不可变接收者的调用运算符的版本。可以多次调用,但不可改变闭包捕获的变量。请勿将此 trait (Fn) 与 函数指针 (fn) 混淆。 - 实现:

pub trait Fn<Args>: FnMut<Args>where
    Args: Tuple,{
    // Required method
    extern "rust-call" fn call(&self, args: Args) -> Self::Output;
}

调用一个闭包

let square = |x| x * x;
assert_eq!(square(5), 25);

使用 Fn 参数

fn call_with_one<F>(func: F) -> usize
    where F: Fn(usize) -> usize {
    func(1)
}

let double = |x| x * 2;
assert_eq!(call_with_one(double), 2);
  • 其它示例
fn main() {
    fn call_fn<T>(x: i64, y: i64, fun: T) -> i64
    where
        T: Fn(i64, i64) -> i64,
    {
        fun(x, y)
    }
    let fun1: fn(i64, i64) -> i64 = |x, y| x + y;
    println!("call_fn(1,2)={}", call_fn(1, 2, fun1));
}

8.2 FnMut

https://www.rustwiki.org.cn/zh-CN/std/ops/trait.FnMut.html - 功能:采用可变接收者的调用运算符的版本。可以多次调用,可以改变闭包捕获的变量。 - 实现:

pub trait FnMut<Args>: FnOnce<Args>where
    Args: Tuple,{
    // Required method
    extern "rust-call" fn call_mut(
        &mut self,
        args: Args
    ) -> Self::Output;
}

调用可变捕获闭包

let mut x = 5;
{
    let mut square_x = || x *= x;
    square_x();
}
assert_eq!(x, 25);

使用 FnMut 参数

fn do_twice<F>(mut func: F)
    where F: FnMut()
{
    func();
    func();
}

let mut x: usize = 1;
{
    let add_two_to_x = || x += 2;
    do_twice(add_two_to_x);
}

assert_eq!(x, 5);

其它示例

fn main() {
    fn call_fnmut<T>(x: i64, y: i64, mut fun: T) -> i64
    where
        T: FnMut(i64, i64) -> i64,
    {
        fun(x, y)
    }
    let mut z: usize = 2;
    println!(
        "call_fnmut(2,3)={}",
        call_fnmut(2, 3, |x: i64, y: i64| {
            z += 1;
            return x + y;
        })
    );
}

8.3 FnOnce

https://www.rustwiki.org.cn/zh-CN/std/ops/trait.FnOnce.html - 功能:具有按值接收者的调用运算符的版本。只能调用一次。 - 实现:

pub trait FnOnce<Args>where
    Args: Tuple,{
    type Output;

    // Required method
    extern "rust-call" fn call_once(self, args: Args) -> Self::Output;
}

使用 FnOnce 参数

fn consume_with_relish<F>(func: F)
    where F: FnOnce() -> String
{
    // `func` 消耗其捕获的变量,因此不能多次运行。
    println!("Consumed: {}", func());

    println!("Delicious!");

    // 再次尝试调用 `func()` 将为 `func` 引发 `use of moved value` 错误。
}

let x = String::from("x");
let consume_and_return_x = move || x;
consume_with_relish(consume_and_return_x);

// 此时无法再调用 `consume_and_return_x`