【Rust 中级教程】 06 trait (4)

202 阅读4分钟

0x00 开篇

本篇文章将继续向大家介绍下在 Rust 标准库中常用和常见的一些 trait

0x01 Sized 和 ?Sized

Sized 和 UnSized 这是一种标记 trait  (marker trait) ,他没有方法或者关联类型。Rust 为其适用的所有类型都自动实现了这个 trait,任何人都不能自己实现。当然它也不可以同 derive 一起使用。

Sized 标识固定大小类型,即他们的值在内存中的大小都是相同的。比如每个 f64 类型占8个字节,i32 类型占4个字节等等。

?Sized 表示非固定大小类型,即他们的值大小并不固定,比如前面文章讲到过的字符串切片类型 &str和 [T] 类型的数组。说白了,非固定大小的类型基本都是占两个字节的胖指针或者是 trait object(有关trait object 的概念暂时不会介绍)。

在我们声明泛型结构体时的时候,通常是 struct<T> 来声明。然而 Rust 则会理解为 struct<T: Sized>。这是因为非固定大小的类型具有非常大的局限性,因此大多数的泛型都被限制使用 Sized 类型。如果你不想限制 T,则需要显示的表示出来。

/// 显式标明
struct MySized<T: ?Sized> {
    value: T,
}

有关这 Sized trait 暂时作为了解即可。

0x02 Default

Default 是 Rust 中提供默认值类型的一个 trait,通常作用于 struct 上。该类型可以同 derive 一起使用,任何人都不能自己。

源码(default.rs):

pub trait Default: Sized {
    fn default() -> Self;
}

源码很简单,就只有一个 default 函数当在结构体上使用 #[derive(Default)] 时,Rust 将会自动为其实现一些基本类型参数的默认值。示例代码如下:

#[derive(Default, Debug)]
struct Test1 {
    a: i32,
    b: f64,
    c: bool,
    d: char,
    e: (),
    f: String,
}

fn main() {
    let test = Test1::default();
    println!("{:#?}", test);
    // 运行结果:
    // Test1 {
    //     a: 0,
    //     b: 0.0,
    //     c: false,
    //     d: '\0',
    //     e: (),
    //     f: "",
    // }
}

其实参数的默认值我们也可以从源码中找到:

default_impl! { (), (), "Returns the default value of `()`" }
default_impl! { bool, false, "Returns the default value of `false`" }
default_impl! { char, '\x00', "Returns the default value of `\x00`" }

default_impl! { usize, 0, "Returns the default value of `0`" }
default_impl! { u8, 0, "Returns the default value of `0`" }
default_impl! { u16, 0, "Returns the default value of `0`" }
default_impl! { u32, 0, "Returns the default value of `0`" }
default_impl! { u64, 0, "Returns the default value of `0`" }
default_impl! { u128, 0, "Returns the default value of `0`" }

default_impl! { isize, 0, "Returns the default value of `0`" }
default_impl! { i8, 0, "Returns the default value of `0`" }
default_impl! { i16, 0, "Returns the default value of `0`" }
default_impl! { i32, 0, "Returns the default value of `0`" }
default_impl! { i64, 0, "Returns the default value of `0`" }
default_impl! { i128, 0, "Returns the default value of `0`" }

default_impl! { f32, 0.0f32, "Returns the default value of `0.0`" }
default_impl! { f64, 0.0f64, "Returns the default value of `0.0`" }

使用 #[derive(Default)] 标记的结构体,其参数也必须都实现 Default trait。另外,我们也可以自己实现 Default trait。示例代码如下:

#[derive(Debug)]
struct Test2 {
    a: i32,
    b: f64,
    c: bool,
}

/// 默认实现 Default
impl Default for Test2 {
    fn default() -> Self {
        return Self {
            a: 10,
            b: 20.0,
            c: true,
        };
    }
}

fn main() {
    let test2 = Test2::default();
    println!("{:#?}", test2);
    // 运行结果
    // Test2 {
    //     a: 10,
    //     b: 20.0,
    //     c: true,
    // }
}

0x03 编译器做了什么?

看到这里,大家有没有好奇,我们添加 #[derive] 后,编译器到底做了什么呢?其实,我们可以通过 cargo-expand 看到。以Debug 为例。我们将下面的代码通过命令展开。

#[derive(Debug)]
struct Rectangle {
    width: u32,
    height: u32,
}

fn main() {
    let rec = Rectangle {
        width: 3,
        height: 5,
    };
    println!("{:?}", rec);
}

首次使用请先通过以下命令安装。

cargo install cargo-expand

下面的命令必须使用 Rust nightly 版本

 cargo rustc --profile=check -- -Zunpretty=expanded

展开后的代码如下:

#![feature(prelude_import)]                                                                                                                                                             
#[prelude_import]
use std::prelude::rust_2021::*;
#[macro_use]
extern crate std;
struct Rectangle {
    width: u32,
    height: u32,
}
#[automatically_derived]
impl ::core::fmt::Debug for Rectangle {
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        ::core::fmt::Formatter::debug_struct_field2_finish(f, "Rectangle",
            "width", &&self.width, "height", &&self.height)
    }
}

fn main() {
    let rec = Rectangle { width: 3, height: 5 };
    {
        ::std::io::_print(::core::fmt::Arguments::new_v1(&["", "\n"],
                &[::core::fmt::ArgumentV1::new_debug(&rec)]));
    };
}

很清楚的可以看到,编译器为 Rectangle 结构体实现了 Debug trait。

0x04 小结

有关常用的一些 trait 暂时先介绍到这里,当然其实还有一些 trait 我还没有写出来, 比如:CopyClone 等。其实介绍这些 trait 还需要一些前置知识的了解。

在 Rust 中,trait 类型是最重要的类型,它是 Rust 中唯一的接口抽象方式,可以很方便的让开发者在多种类型上定义统一的行为,这对于项目开发尤为重要。有关 trait 的文章目前已经有4篇了,我想可以暂告一个段落了。这里还留了一个小尾巴—— trait object,我也将会放到后面去介绍。