本教程环境
系统:MacOS
Rust 版本:1.77.2
Drop
一个值的拥有者消失时,Rust 会丢弃(drop)该值。丢弃这个值的时候会丢弃这个值拥有的其他的值、堆存储和系统资源。
在大多数情况下,Rust 会自动处理丢弃工作,会调用 drop
方法。
可以通过实现 std::ops::Drop
特型来自定义当值被丢弃时调用的 drop
方法。drop
方法不能手动调用。
drop
只能被 Rust 在合适时机隐式的调用。
fn main() {
let mut _a = Appellation {
name: "Zeus".to_string(),
nickname: vec!["cloud collector".to_string(), "king of the gods".to_string()]
};
println!("before assignment");
_a = Appellation {
name: "Hera".to_string(),
nickname: vec![]
};
println!("at end of block");
}
struct Appellation {
name: String,
nickname: Vec<String>
}
impl Drop for Appellation {
fn drop(&mut self) {
print!("Dropping {}", self.name);
if !self.nickname.is_empty() {
print!(" (AKA {})", self.nickname.join(", "));
}
println!();
}
}
// before assignment
// Dropping Zeus (AKA cloud collector, king of the gods)
// at end of block
// Dropping Hera
Sized
Sized
表示固定大小类型,指每个值在内存中都有相同的大小。
所有固定大小的类型都实现了 std::marker::Sized
特型。该特型没有方法和关联类型。Rust 自动为所有适用的类型实现了 std::marker::Sized
特型,不能自己去实现。
**Sized**
**的唯一用途是作为类型变量的限界。**例如 T: Sized
。
Rust 有一些无固定大小的类型,切片对象。例如,字符串切片类型 str
就是无固定大小类型。字符串字面量 diminutive
和 big
是对占用了 10 字节和 3 字节的 str
切片的引用。像[T]
这样的数组切片类型也是无固定大小的。
还用 **dyn**
类型也是无固定大小的,它是特型对象的引用目标。
特型对象是指向实现了给定特型的某个值的指针。
Sized
是 Rust 中泛型类型变量的隐式限定,默认要求固定大小。例如,struct S<T> {}
,Rust 会理解为 struct S<T: Sized> {}
。
如果不要求固定大小,需要使用 ?Sized
。struct S<T: ?Sized> {}
。
还有一种无固定大小类型,结构体类型的最后一个字段(只能是最后一个字段)可以是无固定大小的,并且这样的结构体本身也是无固定大小的。例如,Rc<T>
引用计数指针的内部实现是指向私有类型 RcBox<T>
的指针,后者把引用计数和 T
保存在一起。下面是 RcBox
的简化定义:
struct RcBox<T: ?Sized> {
ref_count: usize,
value: T,
}
Rc<T>
是引用计数指针,其中 value 字段是 Rc<T>
对其进行引用计数的 T 类型。Rc<T>
会解引用成指向 value
字段的指针。ref_count
字段会保存其引用记数。
Clone
std::clone::Clone
特型适用于复制自身类型。定义如下:
trait Clone: Sized {
fn clone(&self) -> Self;
fn clone_from(&mut self, source: &self) {
*self = source.clone()
}
}
克隆一个值还需要为它拥有的任何值分配副本,因此在时间消耗和内存占用方面都是昂贵的。Rust 不会自动进行克隆,需要显式的调用。
clone_from
会把 self
修改为 source
的副本。
如果需要定义的 Clone 实现只是简单的对类型中的每个字段或元素进行 clone,那么可以使用属性 #[derive(Clone)]
。
std::fs::File
类型可能会复制失败,所以它提供了一个 try_clone
方法,该方法会返回一个 std::io::Result<File>
值,用于报告失败信息。
Copy
std::marker::Copy
是标记特型。
trait Copy: Clone {}
实现了 Copy 特型的类型,在进行赋值的时候不会进行移动,而是复制。
可使用 #[derive(Copy)]
来派生出 Copy 实现。
Deref 与 DerefMut
std::ops::Dref
和 std::ops::DerefMut
是两个面向指针类型的重要特型,允许对指针类型的值进行解引用操作。
trait Deref {
type Target: ?Sized;
fn deref(&self) -> &Self::Target;
}
trait DerefMut: Deref {
fn deref_mut(&mut self) -> &mut Self::Target;
}
例如有一个类型 T
和类型 T
的引用 &T
,那么 Rust 可以使用 *(&T)
来得到 T
类型的值。
但是,如果有一个自定义的智能指针类型,Rust 就不知道如果使用 *
来进行解引用。
DerefMut特型,与Deref特型类似,但它用于可变引用的解引用。
示例:
use std::ops::{Deref, DerefMut};
fn main() {
let mut s = Selector {
elements: vec!['x', 'y', 'z'],
current: 2
};
assert_eq!(*s, 'z');
assert!(s.is_alphabetic());
*s = 'w';
assert_eq!(s.elements, ['x', 'y', 'w']);
}
struct Selector<T> {
elements: Vec<T>,
current: usize
}
impl<T> Deref for Selector<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.elements[self.current]
}
}
impl<T> DerefMut for Selector<T> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.elements[self.current]
}
}
Default
实现std::default::Default
特型,为类型设置默认值。例如,向量或字符串默认为空、数值默认为 0、Option
默认为 None
。
trait Default {
fn default() -> Self;
}
为 String
实现 Default
。
impl Default for String {
fn default() -> String {
String::new()
}
}
如果类型 T
实现了 Default
,那么标准库就会自动为 Rc<T>
、Arc<T>
、Box<T>
、Cell<T>
、RefCell<T>
、Cow<T>
、Mutex<T>
和 RwLock<T>
实现 Default
。例如,类型 Rc<T>
的默认值就是一个指向类型 T
的默认值的 Rc
。
Rust 不会为结构体类型隐式实现 Default
,如果结构体的每个字段都实现了 Default
,可使用 #[derive(Default)]
为次结构体实现 Default
。
AsRef 与 AsMut
允许从一种类型获取另一种类型的共享引用或可变引用。 如果一个类型实现了 AsRef<T>
,那么就意味着可以高效的从中借入 &T
。AsMut
是AsRef
针对可变引用的对应类型。
trait AsRef<T: ?Sized> {
fn as_ref(&self) -> &T;
}
trait AsMut<T: ?Sized> {
fn as_mut(&mut self) -> &mut T;
}
**AsRef**
**通常用于让函数更灵活的接受其参数类型。**例如,std::fs::File::open
函数的声明如下:
fn open<P: AsRef<Path>>(path: P) -> Result<File>
open
函数真正想要的是 &Path
,即代表文件系统路径的类型。有了这个签名,open
就能接受可以从中借入&Path
的一切,也就是实现了AsRef<Path>
的一切。这些类型包括 String
和 str
、操作系统接口字符串类型 OsString
和 OsStr
、PathBuf
和Path
。
Borrow 与 BorrowMut
std::borrow::Borrow
特型类似于 AsRef
,如果一个类型实现了 Borrow<T>
,那么它的borrow
方法就能高效地从自身借入一个&T
。但是 Borrow
施加了更多的限制:只有当 &T
能通过与它借来的值相同的方式进行哈希和比较时,此类型才应实现 Borrow<T>
。
这在区分对 String
的借用时很重要,比如 String
实现了 AsRef<str>
、AsRef<[u8]>
和 AsRef<Path>
,但这 3 种目标类型通常具有不一样的哈希值。只有 &str
切片才能保证像其等效的 String 一样进行哈希,因此 String
只实现了 Borrow<str>
。
trait Borrow<Borrowed: ?Sized> {
fn borrow(&self) -> &Borrowed;
}
Borrow
旨在解决具有泛型哈希表和其他关联集合类型的特定情况。假设有一个 std::collections::HashMap<String, i32>
,用于将字符串映射到值。这个表的键是 String
,那么如何查找一个条目?
impl<K, V> HashMap<K, V> where K: Eq + Hash {
fn get(&self, key: K) -> Option<&V> { }
}
在这里 K
是 String
,每次必须将 String
按值传递进行调用,显然是一种浪费。真正需要的只是键的引用。
impl<K, V> HashMap<K, V> where K: Eq + Hash {
fn get(&self, key: &K) -> Option<&V> {}
}
此时必须将键作为 &String
进行传递。如果要查找常量字符串,就必须像下面的写法。
hashtable.get(&"twenty-two".to_string());
它会在堆上分配一个 String
缓冲区并将文本复制进去,这样才能将其作为 &String
借用出来,传给 get
,然后将其丢弃。
但是真实的应该传入任何可以哈希并与我们的键类型进行比较的类型。例如,&str
就可以了。
impl<K, V> HashMap<K, V> where K: Eq + Hash {
fn get<Q: ?Sized>(&self, key: &Q) -> Option<&V>
where K: Borrow<Q>,
Q: Eq + Hash
{}
}
BorrowMut
特型类似于针对可变引用的 Borrow
。
triat BorrowMut<Borrowed: ?Sized>: Borrow<Borrowed> {
fn borrow_mut(&mut self) -> &mut Borrowed;
}
From 与 Into
std::convert::From
和std::convert::Into
表示类型转换,这种转换会接收一种类型的值并转换为另一种类型的值。From
和Into
会获取其参数的所有权,对其进行转换,然后将转换结果的所有权返回给调用者。
From
和Into
的定义是对称的:
trait Into<T>: Sized {
fn into(self) -> T;
}
trait From<T>: Sized {
fn from(other: 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]
数组,因为这些类型都恰好实现了 Into<Ipv4Addr>
。
From
特型的 from
方法会充当泛型构造函数,用于从另一个值生成本类型的实例。
let addr1 = Ipv4Addr::from([66, 146, 219, 98]);
let addr2 = IPv4Addr::from(0xd076eb94_u32);
给定 From
实现,标准库会自动实现对应的 Into
特型。
TryFrom 与 TryInto
是 From
和Into
的容错版本。返回的是一个 Result
结果。
ToOwned
std:borrow::ToOwned
特型提供了一种稍微宽松的方式来引用转换为拥有型的值:
trait ToOwned {
type Owned: Borrow<Self>;
fn to_owned(&self) -> Self::Owned;
}
与必须返回 Self
类型的 clone
不同,to_owned
可以返回任何能让你从中借入 &Self
的类型:Owned
类型必须实现 Borrow<Self>
。
Borrow 与 ToOwned 的实际运用:Cow
有时候,在程序开始运行之前无法决定是该借用还是该拥有,std::borrow::Cow
类型提供了兼顾两者的方式。
enum Cow<'a, B: ?Sized>
where B: ToOwned
{
Borrowed(&'a B),
Owned(<B as ToOwned>::Owned),
}
Cow<B>
要么借入对 B 的共享引用,要么拥有可供借入此类引用的值。
Cow
的一个常见的用途是返回静态分配的字符串常量或由计算得来的字符串。假设需要将错误美剧转换为错误消息。可以返回 Cow<'static, str>
:
use std::borrow::Cow;
use std::path::PathBuf;
#[derive(Debug)]
enum Error {
OutOfMemory,
StackOverflow,
MachineOnFire,
Unfathomable,
FileNotFound(PathBuf)
}
fn describe(error: &Err) -> Cow<'static, str> {
match *error {
Error::OutOfMemory => "out of memory".into(),
Error::StackOverflow => "stack overflow".into(),
Error::MachineOnFire => "machine on file".into(),
Error::Unfathomable => "machine bewildered".into(),
Error::FileNotFound(ref path) => {
format!("file not found: {}", path.display()).into()
}
}
}
match
语句的大多数分支会返回 Cow::Borrowed
来引用静态分配的字符串。FileNotFound
变体,会使用 format!
来构建包含给定文件名的消息,这个分支生成 Cow::Owned
值。
如果describe
的调用者不打算更该值,可以直接把此 Cow
看作&str
:
println!("Disaster has struck: {}", describe(&error));
如果调用者确实需要一个拥有型的值,那么也能很容易生成一个:
let mut log: Vec<String> = Vec::new();
// ...
log.push(describe(&error).into_owned());
参考链接:
🌟 🙏🙏感谢您的阅读,如果对您有帮助,欢迎关注、点赞 🌟🌟