Rust 常用特征详解

2022-09-19 rust language

借用

有很多特征可以用来获取借用,包括了 Borrow BorrowMut ToOwned Cow AsRef 等。

AsRef

其定义在 std::convert 中,相似的还有 AsMut Into From 等特征。

pub trait AsRef<T: ?Sized> {
    fn as_ref(&self) -> &T;
}
pub trait AsMut<T: ?Sized> {
    fn as_mut(&mut self) -> &mut T;
}

简单来说:

  • 如果 U 实现了 AsRef<T>,那么 as_ref() 可以实现 &U&T 的转换。
  • 如果 U 实现了 AsMut<T>,那么 as_ref() 可以实现 &U&mut T 的转换。

另外,为了方便使用,还实现了如下的泛型。

// As lifts over &
impl<T: ?Sized, U: ?Sized> AsRef<U> for &T
where
    T: AsRef<U>,
{
    #[inline]
    fn as_ref(&self) -> &U {
        <T as AsRef<U>>::as_ref(*self)
    }
}

// As lifts over &mut
impl<T: ?Sized, U: ?Sized> AsRef<U> for &mut T
where
    T: AsRef<U>,
{
    #[inline]
    fn as_ref(&self) -> &U {
        <T as AsRef<U>>::as_ref(*self)
    }
}

// AsMut lifts over &mut
impl<T: ?Sized, U: ?Sized> AsMut<U> for &mut T
where
    T: AsMut<U>,
{
    #[inline]
    fn as_mut(&mut self) -> &mut U {
        (*self).as_mut()
    }
}

也就是说:

  • 如果 T 实现了 AsRef<U>,那么 &T 就实现了 AsRef<U>
  • 如果 T 实现了 AsRef<U>,那么 &mut T 就实现了 AsRef<U>
  • 如果 T 实现了 AsMut<U>,那么 &mut T 就实现了 AsMut<U>

以如下的 std::fs::File::open() 函数为例,该函数期望的参数是 &Path 类型,而实际上传递 String str 类型也都可以。

pub fn open<P: AsRef<Path>>(path: P) -> io::Result<File> {
    OpenOptions::new().read(true).open(path.as_ref())
}

Path 的实现 std/path.rs 中可以看到如下的实现,这里是通过新建对象实现。

impl AsRef<Path> for String {
    fn as_ref(&self) -> &Path {
        Path::new(self)
    }
}

Borrow

很多时候不同场景会对类型进行一些封装,例如 Box<T> Rc<T> 以及 Stringstr 等,除了表面的数据类型之外,还有其底层的数据类型,那么当获取 (或者 Borrow) 其底层数据时,是否要保持相同的 Eq Ord Hash 等特征。

也就是说,对于 x.borrow() == y.borrow()x == y 的结果应该相同。

Sized

用来表示在编译阶段即可确定大小的类型,除此之外还有 ?Sized 表示同时支持动态大小类型,也就是 Dynamically Size Types, DSTs 类型,常见的有:切片 &[T]、字符串切片 &str、Trait Object &dyn Trait 以及包含上述 DST 的类。

在 Rust 中,引用或者原始指针采用 usize 大小的内存保存,称为瘦指针 (Thin Pointer),而 DST 类型也被称为胖指针 (Fat Pointer) 或者 Pointer Metadata,会包含额外 usize 大小的指针指向元数据。

不同类型保存的元数据信息不同,例如切片 (含字符串切片) 保存的是长度,而 Trait Object 保存的是虚拟表。

#![feature(ptr_metadata)]
#![allow(dead_code)]

use std::ptr::metadata;

trait Shape {
    fn area(&self) -> f64;
}

struct Rectangle {
    width: f64,
    height: f64,
}
impl Shape for Rectangle {
    fn area(&self) -> f64 {
        self.width * self.height
    }
}

fn main() {
    let slice: &[u8] = &[1, 2, 3];
    assert_eq!(metadata(slice), 3);

    let string = "Hello";
    assert_eq!(metadata(string), 5);

    struct Wrapper<T: ?Sized> {
        value: u32,
        data: T,
    }
    let wrapper: &Wrapper<[u8]> = &Wrapper {
        value: 0,
        data: [0, 1, 2],
    };
    assert_eq!(metadata(wrapper), 3);

    let thin: u8 = 2;
    assert_eq!(metadata(&thin), ());

    let rect: &dyn Shape = &Rectangle {
        width: 10.0,
        height: 2.0,
    };
    //assert_eq!(metadata(&thin), DynMetadata::<Shape>);
    println!("{:?}", metadata(rect));
}

其它

  • #[allow(dead_code)] 允许部分代码未调用,当采用 #! 时表示 crate 内,通常在测试、演示场景中使用。