总结 Rust 常用内置 trait

本教程环境
系统:MacOS
Rust 版本:1.77.2

Rust 常用标准库 trait.png

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 就是无固定大小类型。字符串字面量 diminutivebig是对占用了 10 字节和 3 字节的 str 切片的引用。像[T]这样的数组切片类型也是无固定大小的。
还用 **dyn**类型也是无固定大小的,它是特型对象的引用目标。

特型对象是指向实现了给定特型的某个值的指针。

Sized是 Rust 中泛型类型变量的隐式限定,默认要求固定大小。例如,struct S<T> {},Rust 会理解为 struct S<T: Sized> {}
如果不要求固定大小,需要使用 ?Sizedstruct 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::Drefstd::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>,那么就意味着可以高效的从中借入 &TAsMutAsRef针对可变引用的对应类型。

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> 的一切。这些类型包括 Stringstr、操作系统接口字符串类型 OsStringOsStrPathBufPath

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> { }
}

在这里 KString,每次必须将 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::Fromstd::convert::Into表示类型转换,这种转换会接收一种类型的值并转换为另一种类型的值。FromInto会获取其参数的所有权,对其进行转换,然后将转换结果的所有权返回给调用者。
FromInto的定义是对称的:

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

FromInto的容错版本。返回的是一个 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());

参考链接:

🌟 🙏🙏感谢您的阅读,如果对您有帮助,欢迎关注、点赞 🌟🌟

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值