特型 | 简介 |
---|---|
Drop | 解构函数。清除值时 Rust 自动运行的清除代码 |
Sized | 标记特型,针对编译时可以知道大小的类型(而不是像切片那样动态大小的类型) |
Clone | 针对支持克隆值的类型 |
Copy | 标记特型,针对可以简单地对内存中包含的值进行逐字节复制来克隆的类型 |
Deref 与 DerefMut | 智能指针类型的特型 |
Default | 针对有合理 “默认值” 的类型 |
AsRef 与 AsMut | 转换特型,借用某种类型的引用 |
Borrow 与 BorrowMut | 转换特型,类似 AsRef 与 AsMut ,但额外保证一致的散列、顺序和相等 |
From 与 Into | 转换特型,将某种类型的值转换为另一种类型 |
ToOwned | 转换特型,将引用转换为所有值 |
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) { }
_x
的值,与清除其他变量的值一样。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
特型,这个特型没有方法或关联类型。
应用场景:绑定类型变量。
T: Sized
绑定,要求 T
必须是一个编译时大小已知的类型。大多数泛型变量默认被 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
,也可能不是。
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>
。克隆必须万无一失。
Copy
赋值会转移值,而不是复制值。转移值更有利于跟踪变量所拥有的资源。
不拥有任何资源的简单类型可以是 Copy
类型,这种类型的赋值会生成值的副本,而不是转移值并让原始变量变成未初始化。
如果类型实现了 std::marker::Copy
标记特型,那么它就是 Copy
类型。
trait Copy: Clone { }
自定义类型实现它也很简单:
impl Copy for MyType { }
实现 Copy
的类型必须遵守的规则:
Copy
。Copy
。Drop
特型的类型不能是 Copy
。如果一个类型需要特殊的清理代码,那就一定需要特殊的复制代码,因此不能是 Copy
类型。特型派生:
#[derive(Copy)]
让类型派生 Copy
;#[derive(Clone, Copy)]
让类型同时派生 Clone
和 Copy
。Deref
与 DerefMut
std::ops::Deref
与 std::ops::DerefMut
特型可以修改解引用操作符 *
和.
在自定义类型上的行为。
Box<T>
和 Rc<T>
这样的指针类型实现了这两个特型。如果有一个 Box<Complex>
类型的值 b
,那么 *b
引用的就是 b
指向 Complex
的值,而 < 引用的是其实数部分。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]>
。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
。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;
}
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>
。
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();
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>
;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 程序设计》(吉姆 - 布兰迪、贾森 - 奥伦多夫著,李松峰译)第十三章
原文地址
本文发布于:2024-01-29 20:02:43,感谢您对本站的认可!
本文链接:https://www.4u4v.net/it/170652976717936.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
留言与评论(共有 0 条评论) |