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 我还没有写出来, 比如:Copy,Clone 等。其实介绍这些 trait 还需要一些前置知识的了解。
在 Rust 中,trait 类型是最重要的类型,它是 Rust 中唯一的接口抽象方式,可以很方便的让开发者在多种类型上定义统一的行为,这对于项目开发尤为重要。有关 trait 的文章目前已经有4篇了,我想可以暂告一个段落了。这里还留了一个小尾巴—— trait object,我也将会放到后面去介绍。