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 derive
d, 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
。在可变上下文中,使用 DerefMut
。Deref
和 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
和Drop
。Copy
类型被编译器隐式复制,这使得很难预测何时以及将执行析构函数的频率。因此,这些类型不能有析构函数。
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: Copy
,x: 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
集合
Vec
、String
和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 中,Fn
、FnMut
和 FnOnce
是 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`