当前位置:网站首页>【Rust 笔记】11-实用特型
【Rust 笔记】11-实用特型
2022-07-03 08:24:00 【phial03】
11 - 实用特型
特型 | 简介 |
---|---|
Drop | 解构函数。清除值时 Rust 自动运行的清除代码 |
Sized | 标记特型,针对编译时可以知道大小的类型(而不是像切片那样动态大小的类型) |
Clone | 针对支持克隆值的类型 |
Copy | 标记特型,针对可以简单地对内存中包含的值进行逐字节复制来克隆的类型 |
Deref 与 DerefMut | 智能指针类型的特型 |
Default | 针对有合理 “默认值” 的类型 |
AsRef 与 AsMut | 转换特型,借用某种类型的引用 |
Borrow 与 BorrowMut | 转换特型,类似 AsRef 与 AsMut ,但额外保证一致的散列、顺序和相等 |
From 与 Into | 转换特型,将某种类型的值转换为另一种类型 |
ToOwned | 转换特型,将引用转换为所有值 |
11.1-Drop
当一个值的所有者离开时,Rust 会自动清除(Drop)这个值。清除值涉及释放相关的值、堆空间,以及该值拥有的系统资源。
Rust 标准特型
std::ops::Drop
:trait Drop { fn drop(&mut self); }
清除会在各种条件下发生,包括变量超出作用域、表达式的值被
;
操作符丢弃、截断向量时从末尾删除其元素,等等。针对自定义类型设计
Drop
:- 结构体和特型设计
struct Appellation { name: String, nicknames: Vec<String> } trait Drop { fn drop(&mut self); } impl Drop for Appellation { fn drop(&mut self) { print!("Dropping {}", self.name); if !self.nicknames.is_empty() { print!(" (AKA {})", self.nicknames.join(", ")); } println!(""); } }
- 测试代码:
fn main() { let mut a = Appellation { name: "Zeus".to_string(), nicknames: vec!["cloud collector".to_string, "king of the gods".to_string()] }; println!("before assignment"); a.drop(); a = Appellation { // 第二次赋值后,第一次的赋值被清除 name: "Hera".to_string(), nicknames:vec![] }; println!("at end of block"); a.drop(); } // a离开作用域,第二次的赋值被清除
- 结果输出
before assignment Dropping Zeus (AKA cloud collector, king of the gods) at end of block Dropping Hera
如果变量的值被转移到其他地方,导致变量在超出作用域时处于未初始化状态,Rust 则不会清除该变量,因为这个变量已经没有值需要清除了。此时,Rust 会用一个不可见的标志跟踪变量的状态,该标志表示变量的值是否需要被清除:
let p; { let q = Appellation { name: "Cardamine hirsuta".to_string(), nicknames: vec!["shotweed".to_string(), "bittercress".to_string()] }; if complicated_condition() { p = q; } } // q的作用域在此处结束 println!("Sproing!"); // p的作用域在此处结束
如果某类型实现了
Drop
特型,就不能再实现Copy
特型。- 如果一个类型包含
Copy
特型,那么简单的字节对字节的赋值就会产生一个值的独立副本。 - 在同一份数据多次调用同一个
drop
方法是错误的。
- 如果一个类型包含
标准前置模块中包含一个清除值的函数:
fn drop<T>(_x: T) { }
- 接收一个参数的值,从调用者那里取得所有权,然后什么也不做。
- Rust 会在超出作用域时,清除
_x
的值,与清除其他变量的值一样。
11.2-Sized
固定大小的类型(sized type):其值在内存中都具有相同大小。
- 每个
u64
占 8 个字节。 - 每个
(f32, f32, f32)
元组占 12 个字节。 - 对于枚举类型,其值始终占能保存其最大变体的空间。
Vec<T>
拥有分配在堆上的大小可变的缓冲区,但是Vec
值本身值包含一个指向该缓冲区的指针、缓冲区的容量及其长度。因此Vec<T>
也是固定大小的类型。
- 每个
非固定大小类型(unsized type):其值在内存的大小并不固定。如
str
和[T]
类型表示大小不确定的值的集合,所以它们是非固定大小的。- 字符串切片类型
str
(注意没有&
)是非固定大小的。字符串字面量"diminutive"
和"big"
是对 19 和 3 字节str
切片的引用。 - 如
[T]
(同样没有&
)这样的数组切片类型也是非固定大小的,即共享引用&[u8]
可以指向任意大小的[u8]
切片。
- 字符串切片类型
对特型目标的引用也是非固定大小类型。
- 特性目标是一个指向实现了给定特型的某个值的指针。如
&str::io::Write
和Box<std::io::Write>
都是指向了实现Write
特型的某个值的指针。 - 引用目标可能是一个文件、一个网络套接口,或者实现了
Write
的自定义类型。 - 因为实现
Write
的类型是可以扩充的,所有Write
作为类型被认为是非固定大小的,即其值的大小可变。
- 特性目标是一个指向实现了给定特型的某个值的指针。如
结构体的最后一个字段可能是非固定大小的。此时,结构体本身也是非固定大小的。
Rc<T>
引用计数指针在内部被实现为一个指向私有类型RcBox<T>
的指针,该类型用于保存类型T
及其引用计数。struct RcBox<T: Sized> { ref_count: usize, value: T, // }
value
字段的值是T
,对它保存的Rc<T>
的引用计数。Rc<T>
解引用为一个对这个字段的指针。ref_count
字段保存引用计数。
指向非固定大小值的指针都是胖指针,占两个字宽。
- 胖指针既包含指向切片的指针,也包含切片的长度。
- 特型目标也包含一个指向方法实现的虚拟表的指针。
所有固定大小的类型都实现了
std::marker::Sized
特型,这个特型没有方法或关联类型。- Rust 为其适用的所有类型自动实现了这个特型。
- 开发者不能自己实现。
应用场景:绑定类型变量。
T: Sized
绑定,要求T
必须是一个编译时大小已知的类型。- 这种特型称为标记特型(marker trait),可以将某些类型标记为具有关注的特征。
大多数泛型变量默认被 Rust 限制为使用
Sized
类型。struct S<T> {...}
会被 Rust 理解为struct S<T: Sized> {...}
。- 如果开发者写成
struct S<T: ?Sized> {b: Box<T>}
(其含义是 “不一定是Sized
”),那么 Rust 会允许使用S<str>
和S<Write>
,定义为胖指针;还允许使用S<i32>
和S<String>
,定义为普通指针。
不知是否固定大小(questionably sized):类型变量具有
?Sized
绑定,使得这个类型可能是Sized
,也可能不是。
11.3-Clone
std::clone::Clone
特型:适用于可以复制自身的类型。trait Clone: Sized { fn clone(&self) -> Self; fn clone_from(&mut self, source: &Self) { // 将self修改为source的一个副本。 *self = source.clone() } }
Rust 不自动克隆值,而是要求明确调用一个方法:
- 克隆一个值通常涉及创建该值所拥有一切内容的副本及分配内存,因此
clone
无论在时间消耗还是内存占用,都可能比较昂贵。 - 如克隆
Vec<String>
不仅仅要复制向量,还要复制其包含的所有Sring
元素。 Rc<T>
和Arc<T>
这样的引用计数指针属于例外,克隆它们智慧简单地递增相应的引用计数,然后返回新指针。
- 克隆一个值通常涉及创建该值所拥有一切内容的副本及分配内存,因此
有
Clone
特型的类型举例:许多执行复制操作的有意义的类型,都实现了Clone
。- 原始类型
bool
和i32
; - 容器类型
String
、Vec<T>
和HashMap
;
- 原始类型
无
Clone
特型的类型举例:有些类型实现复制操作没有意义。std::sync::Mutex
;std::fs::File
在操作系统没有必要资源时复制会失败。但它提供了一个try_clone
方法,这个方法返回可以报告错误的std::io::Result<File>
。
克隆必须万无一失。
11.4-Copy
赋值会转移值,而不是复制值。转移值更有利于跟踪变量所拥有的资源。
不拥有任何资源的简单类型可以是
Copy
类型,这种类型的赋值会生成值的副本,而不是转移值并让原始变量变成未初始化。如果类型实现了
std::marker::Copy
标记特型,那么它就是Copy
类型。trait Copy: Clone { }
自定义类型实现它也很简单:
impl Copy for MyType { }
实现
Copy
的类型必须遵守的规则:- Rust 只允许类型在字节对字节的深度复制能满足要求的情况下实现
Copy
。 - 那些可能拥有任意资源,比如堆缓冲区或操作系统勾柄的类型,不能实现
Copy
。 - 任何实现
Drop
特型的类型不能是Copy
。如果一个类型需要特殊的清理代码,那就一定需要特殊的复制代码,因此不能是Copy
类型。
- Rust 只允许类型在字节对字节的深度复制能满足要求的情况下实现
特型派生:
#[derive(Copy)]
让类型派生Copy
;#[derive(Clone, Copy)]
让类型同时派生Clone
和Copy
。
11.5-Deref
与 DerefMut
std::ops::Deref
与std::ops::DerefMut
特型可以修改解引用操作符*
和.
在自定义类型上的行为。Box<T>
和Rc<T>
这样的指针类型实现了这两个特型。如果有一个Box<Complex>
类型的值b
,那么*b
引用的就是b
指向Complex
的值,而b.re
引用的是其实数部分。- 上下文赋值,以及从引用目标借用可修改的引用,那么会使用
DerefMut
(可变解引用)特型。 Deref
只取得只读权限。
特型定义:
trait Deref { type Target: ?Sized; fn deref(&self) -> &Self::Target; } trait DerefMut: Deref { fn deref_mut(&mut self) -> &mut Self::Target; }
deref
和deref_mut
方法接收&Self
引用并返回&Self::Target
引用。Target
是Self
包含、拥有或引用的资源。对于Box<Complex>
来说,Target
的类型就是Complex
。DerefMut
是Deref
的扩展:如果可以解引用并修改资源,那么就可以借用一个对它的共享引用。- 这两个方法返回的引用具有与
&self
一样长的生命期,所以self
会在返回引用的生命期内始终保持被借用。
解引用强制转型(deref coercion):一种类型被 “强制” 表现出另一种类型的行为。比如实现
DerefMut
可以实现对可修改引用的类型转换。- 对于
Rc<String>
的值r
,如果要对它调用String::find
,那么可以简单地写作r.find('?')
。此时&Rc<String>
被强制转换为&String
,因为Rc<T>
实现了Deref<Target=T>
。 - 可以在
String
值上使用Split_at
等方法,即使Split_at
是Str
切片类型的方法,因为String
实现了Deref<Target=str>
。此时&String
强制转型为&str
。 - 如果字节向量
v
,为它传递一个期待字节切片&[u8]
的函数,那么可以将&v
作为参数,因为Vec<T>
实现了Deref<Target=[T]>
。 - Rust 不会尝试解引用强制转型去满足类型变量绑定。
- 对于
11.6-Default
有默认值的类型都实现了
std::default::Default
特型:trait Default { fn default() -> Self; }
String
对Default
的实现如下所示:impl Default for String { fn default() -> String { String::new() } }
Default
可以表示大量参数集合(大部分参数通常不需要改变)的结构体生成默认值。如果类型
T
实现了Default
,那么标准库会自动为Rc<T>
、Arc<T>
、Box<T>
、Cell<T>
、RefCell<T>
、Cow<T>
、Mutex<T>
和RwLock<T>
实现Default
。- 如果元组类型的所有元素类型都实现了
Default
,且该元组类型也实现了Default
,那么这个元组默认会持有每个元素的默认值。 - 如果结构体的所有字段都实现了
Default
,则可以使用#[derive(Default)]
自动为结构体实现Default
。 - 任何
Option<T>
的默认值都是None
。
- 如果元组类型的所有元素类型都实现了
11.7-AsRef
与 AsMut
如果一个类型实现了 AsRef<T>
,那么就可以向它借用一个 &T
。即 AsRef
是共享引用,同样 AsMut
就是可修改引用。
trait AsRef<T: ?Sized> {
fn as_ref(&self) -> &T;
}
trait AsMut<T: ?Sized> {
fn as_mut(&mut self) -> &mut T;
}
11.8-Borrow
与 BorrowMut
如果一个类型实现了
Borrow<T>
,那么它的borrow
方法可以从自身有效地借用一个&T
。不同于AsRef
:只有当&T
与它所借用的值具有相同的散列和比较特性时,一个类型才可以实现Borrow<T>
。trait Borrow<Borrowed: ?Sized> { fn borrow(&self) -> &Borrowed; }
应用场景:
- 常用于处理散列表或树中的键。
- 处理由于其他原因将被散列或比较的值。
String
实现了AsRef<str>
、AsRef<u8>
和AsRef<Path>
,但这三种目标类型通常会有不同的散列值。只有&str
切片能保证与对应 的String
有一样的散列化结果,因此String
只实现了Borrow<str>
。
11.9-From
与 Into
std::convert::From
和std::convert::Into
特型表示类型转换,即消费一种类型的值,然后返回另一种类型的值。AsRef
和AsMut
特型是从一种类型借用另一种类型的引用;From
和Into
则是取得它们参数的所有权,转换类型,再把结果的所有权返回给调用者。
// 这两个特型的定义是对称的 trait Into<T>: Sized { fn into(self) -> T; } trait From<T>: Sized { fn from(T) -> Self; }
标准库中的每种类型
T
都实现了From<T>
和Into<T>
特型。使用
Into
可以让然乎更灵活地接收参数:use std::net::Ipv4Addr; fn ping<A>(address: A) -> std::io::Result<bool> where A: Into<Ipv4Addr> { let ipv4_address = address.into(); ... }
ping
函数可以接收Ipv4Addr
作为参数,也可以接收u32
或[u8; 4]
的数组。u32
和[u8; 4]
的数组都实现了Into<Ipv4Addr>
特型。调用上述特型:
println!("{:?}", ping(Ipv4Addr::new(23, 21, 68, 141))); // 传入Ipv4Addr println!("{:?}", ping([66, 146, 219, 98])); // 传入[u8; 4] println!("{:?}", ping(0xd076eb94_u32)); // 传入u32
From
只实现了From<[u8; 4]>
和From<u32>
:let addr1 = Ipv4Addr::from([66, 146, 219, 98]); let addr2 = Ipv4Addr::from(0xd076eb94_u32);
转换过程中可以使用原始值的资源来构建转换后的值。
let text = "Beautiful Soup".to_string(); let bytes: Vec<u8> = text.into();
11.10-ToOwned
Clone
实现了引用目标的克隆;ToOwned
则实现了引用自身的克隆。它可以把引用转换为所有型的值:trait ToOwned { type Owned: Borrow<Self>; fn to_owned(&self) -> Self::Owned; // 可以返回能够借用为&Self的任何类型 }
可以从
Vec<T>
借用一个&[T]
,因此[T]
可以实现ToOwned<Owned=Vec<T>>
。str
实现了ToOwned<Owned=String>
;Path
实现了ToOwned<Owned=PathBuf>
;
11.11-Borrow
与 ToOwned
实例
一个函数到底应该按引用还是按值接收参数,在某些情况下在程序运行时才能决定到底是借用还是取得所有权更合适。针对此问题,
std::borrow::Cow
可以实现写时克隆(clone on write):enum Cow<'a, B: ?Sized + 'a> where B: ToOwned { Borrowed(&'a B), Owned(<B as ToOwned>::Owned), }
Cow<B>
可以借用对B
的一个共享引用;- 也可以拥有一个值,然后再借用这样的一个引用。
Cow
的用途:返回静态分配的字符串常量或者计算的字符串。将一个错误枚举转换为一条消息:大多数变体可以用固定大小的字符串来处理,但有些也需要在消息中包含额外的数据。可以返回一个
Cow<'static, str>
:use std::path::PathBuf; use std::borrow::Cow; fn describe(error: &Error) -> Cow<'static, str> { match *error { Error::OutOfMemory => "out of memory".into(), Error::StackOverflow => "stack overflow".into(), Error::MachineOnFire => "machine on fire".into(), Error::Unfathomable => "machine bewildered".into(), Error::FileNotFound(ref path) => { format!("file not found: {}", path.display()).into() } } }
describe
的调用者如果不需要修改返回值,可以把Cow
当成一个&str
:println!("Disaster has struck: {}", describe(&error));
当需要一个所有型值的调用者,也可以很容易获取:
let mut log: Vec<String> = Vec::new(); ... log.push(describe(&error).into_owned());
Cow
可以把descirbe
及其调用者得以把内存分配推迟到必要的时刻。
详见《Rust 程序设计》(吉姆 - 布兰迪、贾森 - 奥伦多夫著,李松峰译)第十三章
原文地址
边栏推荐
- Map的实现类的顺序性
- Downward compatibility and upward compatibility
- Pit & ADB wireless debugging of vivo real machine debugging
- Creation and content of mapnode -- osgearth rendering engine series (2)
- About Wireshark's unsuccessful installation of npcap
- Compilation error: "not in executable format: file format not recognized"“
- Flex flexible box layout
- Osgearth topographic shading map drawing
- Scite change background color
- 详解sizeof、strlen、指针和数组等组合题
猜你喜欢
Solution détaillée de toutes les formules de fonction de transfert (fonction d'activation) du réseau neuronal MATLAB
Get to know unity2 for the first time
VIM learning notes from introduction to silk skating
php-fpm软件的安装+openresty高速缓存搭建
CLion-Toolchains are not configured Configure Disable profile问题解决
[cloud native] introduction and use of feign of microservices
Introduction to hexadecimal coding
KunlunBase MeetUP 等您来!
Three characteristics
Shader foundation 01
随机推荐
【更新中】微信小程序学习笔记_3
C language - Introduction - essence Edition - take you into programming (I)
matlab神經網絡所有傳遞函數(激活函數)公式詳解
Huawei interview summary during the epidemic
了解小程序的笔记 2022/7/3
Creation of osgearth earth files to the earth ------ osgearth rendering engine series (1)
C course design employee information management system
Osgconv tool usage
Simply start with the essence and principle of SOM neural network
MySQL containerization (1) docker installation MySQL
Visual Studio (VS) shortcut keys
About Wireshark's unsuccessful installation of npcap
Simple demo of solving BP neural network by gradient descent method
Find the intersection of line segments
图像处理8-CNN图像分类
Chain length value
Detailed explanation of all transfer function (activation function) formulas of MATLAB neural network
Markdown directory generation
Abstract classes and interfaces
Golang string segmentation, substitution and interception