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,我也将会放到后面去介绍。