Rust编程·类型系统·智能指针

 

rust类型、多态的概念 Copy/Clone/Drop, Sized/Send/Sync/Unpin From/Into/AsRef/AsMut, Deref/DerefMut

  • rust类型、多态的概念
  • Copy/Clone/Drop, Sized/Send/Sync/Unpin
  • From/Into/AsRef/AsMut, Deref/DerefMut

类型系统

  • 类型
    • 是对值的区分
    • 它包含了值在内存中的长度
    • 对齐
    • 值可以进行的操作等信息
  • 类型系统
    • 对类型进行定义、检查和处理的系统
  • 多态
    • 本质
      • 使用相同的接口时
      • 不同类型的对象,会采用不同的实现
    • 实现
      • 参数多态(parametric polymorphism)
        • 参数的类型是一个满足某些约束的参数
        • 而非具体的类型
      • 特设多态(adhoc polymorphism)
        • 同一种行为有多个不同实现的多态
      • 子类型多态(subtype polymorphism)
        • 在运行时,子类型可以被当成父类型使用
    • rust
      • 泛型:参数多态
      • 特性:特设多态
      • trait对象:子类型多态

      多态

rust 的类型

  • 类型安全是指代码,只能按照被允许的方法,访问它被授权访问的内存
  • rust 的数据类型
    • 字符、整数、浮点数、布尔值、数组(array)、元组(tuple)、切片(slice)、指针、引用、函数
    • 组合类型
    • 自定义类型
      • 结构体
      • 联合体
  • 类型推导
    • rust可以自己推导出类型,我们可以用Vec<_>让rust自己推导
    • 可以用::<T>早绑定泛型T的类型

泛型

pub enum Cow<'a, B: ?Sized + 'a> where B: ToOwned,
{   // 借用的数据    
    Borrowed(&'a B),    
    // 拥有的数据    
    Owned(<B as ToOwned>::Owned),
}
  • 泛型数据结构
    • enum Option<T>
    • 泛型,是把重复数据结构中的变化抽取出来,作为参数
    • 在使用泛型类型时,根据不同的参数,我们会得到不同的具体类型
  • 泛型约束
    • B: ?Sized + 'a
      • ?Sized 是一种特殊的约束写法
        • ? 代表可以放松问号之后的约束。
        • Rust 默认的泛型参数是 Sized 固定大小的类型,
        • ?Sized 代表用可变大小的类型。
      • 泛型约束可以在想要的地方临时再加,不需要在定义处加上
    • ToOwned 可以把借用的数据克隆出一个拥有所有权的数据

      // 定义一个带有泛型参数 R 的 reader,此处我们不限制 Rstruct 
      MyReader<R> {    
          reader: R,    
          buf: String,
      }
      
      // 定义 process 时,我们需要用到 R 的方法,此时我们限制 R 必须实现 Read trait
      impl<R> MyReader<R>
          where    R: Read,
      {
          ...
      }
      
  • trait对象
  • <B as ToOwned>::Owned
  • B 强制类型转换成 ToOwned trait
  • 然后访问 ToOwned trait 内部的 Owned 类型

rust的类型系统

type

用 Trait 定义接口

  • trait 把数据结构中的行为单独抽取出来,在类型之间共享
  • trait 作为对不同数据结构中相同行为的一种抽象,它可以让我们在开发时,通过用户需求,先敲定系统的行为,把这些行为抽象成 trait,之后再慢慢确定要使用的数据结构,以及如何为数据结构实现这些 trait
  • trait 的类型是不确定的

带关联类型的 Trait

  • 因为比如要返回错误类型,不同的实现者可能返回不同的类型
  • 所以给这个trait一个关联类型,在实现的时候再定下来

为指定类型实现 Trait

// 因为 Add<Rhs = Self> 是个泛型 trait,我们可以为 Complex 实现 Add<f64>
impl Add<f64> for &Complex {    
    type Output = Complex;    // rhs 现在是 f64 了    
    fn add(self, rhs: f64) -> Self::Output {        
        let real = self.real + rhs;        
        Complex::new(real, self.imagine)    
    }
}

为泛型实现 Trait

  • 实现一个从字符串中解析出i32, u32, i64等类型数据的Trait
  • 这个Trait中可以定义一个方法fn parse(&str) -> Result<Self, Self::Error>
  • 这个Result是一个Enum类型,它有两个成员Ok(T)Err(E)
impl<T> Parse for T 
    where T: FromStr + Default 
{
    type Error = String;
}
  • 因为 T 要从&str解析,所以要约定T:FromStr

带泛型的Trait

带泛型的Trait是说,我们可以为Self实现多个其他类型的接口,比如下面的 Add 就可以为不同的Rhs实现不同的add,但是对于关联参数Output,对Self就只能有一个Self::Output不能有多个返回类型。可以认为关联参数与Self是一对一的,泛型与Self无关,可以一对多

要确定一个把数据结构连接起来的 trait,它可以连接String&str,那么这个trait就要用泛型来定义

trait Add<Rhs=Self> {
    type Output;
    #[must_use]
    fn add(self, rhs: Rhs) -> Self::Output;
}

子类型多态 Trait 对象

如果一个对象 A 是对象 B 的子类,那么 A 的实例可以出现在任何期望 B 的实例的上下文中,比如猫和狗都是动物,如果一个函数的接口要求传入一个动物,那么传入猫和狗都是允许的。

Rust通过Trait继承来实现

fn name(animal: impl Animal) -> &'static str {
    animal.name()
}

fn name<T>(animal: T) -> &'static str
    where T: Animal 
{
    animal.name()
}

只要是实现了 Animal Trait 的类型,都可以作为参数。这是静态分发,在编译期就单态化多个函数

对于动态分发,可以使用Trait对象,表现为 &dyn Trait 或者 Box。这里,dyn 关键字只是用来帮助我们更好地区分普通类型和 Trait 类型。比如`Vec<&dyn Formatter>`类型,就是所有实现了`Formatter` Trait的类型

let mut text = "hello world".to_string();
let hmtl: &dyn Formatter = &HtmlFormatter; // struct HtmlFormatter
let rust: &dyn Formatter = &RustFormatter; // struct RustFormatter
let formatters = vec![html, rust];
format(&mut text, formatters);


fn format(input &mut input, formatters: Vec<&dyn Formatter>) {
    for formatter in formatters {
        formatter.format(input);
    }
}

Triat对象:

to

Trait对象的应用:

to2

对象安全

  • 如果 trait 所有的方法,返回值是 Self 或者携带泛型参数,那么这个 trait 就不能产生 trait object
    • trait object 在产生时,原来的类型会被抹去,所以 Self 究竟是谁不知道
    • 不允许携带泛型参数,是因为 Rust 里带泛型的类型在编译时会做单态化,而 trait object 是运行时的产物,两者不能兼容

Trait 总结

trait

必须掌握的trait

  • Clone / Copy trait,约定了数据被深拷贝和浅拷贝的行为;
  • Read / Write trait,约定了对 I/O 读写的行为;
  • Iterator,约定了迭代器的行为;
  • Debug,约定了数据如何被以 debug 的方式显示出来的行为;
  • Default,约定数据类型的缺省值如何产生的行为;
  • From / TryFrom,约定了数据间如何转换的行为。

内存相关 Clone/Copy/Drop

Clone Trait

pub trait Clone {
    fn clone(&self) -> Self;

    fn clone_from(&mut self, source: &Self) {
        *self = source.clone();
    }
}
  • a.clone_from(&b)a = b.clone()的区别
    • b.clone()要分配内存
    • a.clone_from(&b)是在a已经存在的情况下,可以避免内存分配,提高效率
  • 派生宏#[derive(Clone)]直接让自己的数据结构可以Clone
    • 只要内部每个类型都可以Clone
    • Clone 是深度拷贝,栈内存和堆内存一起拷贝
    • clone 方法的接口是 &self,在 clone 一个数据时只需要有已有数据的只读引用
    • 对于 Rc<T> 这样在 clone() 时维护引用计数的数据结构,clone() 过程中会改变自己,所以要用 Cell<T> 这样提供内部可变性的结构来进行改变

Copy Trait

它是一个标记 trait(marker trait),没有方法

pub trait Copy: Clone {}

这样的 trait 虽然没有任何行为,但它可以用作 trait bound 来进行类型安全检查,所以我们管它叫标记 trait

String不能实现Copy,因为如果类型实现了 Copy,那么在赋值、函数调用的时候,值会被拷贝,否则所有权会被移动

不可变引用&可以Copy,但&mut不可以Copy,防止一个作用域下出现多个可变引用

Drop Trait

  • Copy trait 和 Drop trait 是互斥的
  • Copy 是按位做浅拷贝,那么它会默认拷贝的数据没有需要释放的资源

标记 trait:Sized / Send / Sync / Unpin

  • T: Size 这样定义出的泛型结构,在编译期,大小是固定的,可以作为参数传递给函数
  • T: ?Sized,那么 T 就可以是任意大小,可以是 [T] 或者 str 类型

Send / Sync

pub unsafe auto trait Send {}
pub unsafe auto trait Sync {}
  • auto 意味着编译器会在合适的场合,自动为数据结构添加它们的实现
  • unsafe 代表实现的这个 trait 可能会违背 Rust 的内存安全准则,如果开发者手工实现这两个 trait ,要自己为它们的安全性负责
  • Send trait,意味着 T 可以安全地从一个线程移动到另一个线程,也就是说所有权可以在线程间移动
    • 所有权可以移到另一个线程
    • 我可以释放另一个线程的变量
  • Sync trait,则意味着 &T 可以安全地在多个线程中共享
    • 一个类型 T 满足 Sync trait,当且仅当 &T 满足 Send trait
    • Sync就是多线程间的这个变量是同步的

Rc不是Send

nsend

  • Rc不能Send,两个线程试图克隆指向相同引用计数值的 Rc,它们可能会同时尝试更新引用计数
  • Send 可以理解成线程把修改发送给共享的变量

RefCell 不是 Sync

因为 RefCell 可以通过共享引用来修改内部的数据,所以多个线程读它的时候,可能内容是不同的

Arc和Mutex

在多线程间共享数据,只有通过具有Send/Sync能力的sync::Arc和具有Sync能力的sync::Mutex/sync::RwLock

use std::sync::{Arc, Mutex};

fn main() {
    let a = Arc::new(Mutex::new(100));
    let b = a.clone();

    let h = std::thread::spawn(move || {
        let mut x = b.lock().unwrap();
        *x += 100;
    });
    {
        let mut y = a.lock().unwrap();
        *y += 100;
    }

    h.join().unwrap();
    println!("a: {:?}", a);
}

类型转换相关:From / Into/AsRef / AsMut

  • 无设计

      let path = s.to_path();
      let val = s.to_u32();
    
    • 为每一种转换写一个方法
  • 设计方法

      let path = s.into();
      let val: u32 = s.into();
    
    • s和目标类型之间实现一个trait
  • 思想
    • 立足于类型自身,考虑自己如何转换为其他的类型
      • 视角还在具体的类型
      • 每次要扩展自己对新类型的转换,就要修改具体类型的代码
      • 类型自身多了一种功能,那么与这个类型有关的代码,会不会受影响呢?这需要测试才知道
    • 立足于转换功能,考虑一种类型如何转换为另一种类型
      • 视角已经是抽象的类型
      • 只需要添加数据转换Trait的新实现
      • 功能多了一种实现,与它相关的类型的其他功能不受影响,所以与它相关的类型的代码可以不用测试
    • 理解
      • 修改对象/类/函数/模块,相当于换了新的内容,所以与之相关的代码都要测试看看有没有副作用
      • 增加Trait实现,相当于多了一个功能,只要这个功能没有问题,就行
        • 软件中原有对象/类/函数/模块都没有变化
  • 基于这个思路,对值类型的转换和对引用类型的转换,Rust 提供了两套不同的 trait:
    • 值类型到值类型的转换:From<T> / Into<T> / TryFrom<T> / TryInto<T>
    • 引用类型到引用类型的转换:AsRef<T> / AsMut<T>

From / Into

pub trait From<T> {
    fn from(T) -> Self;
}

pub trait Into<T> {
    fn into(self) -> T;
}

impl<T, U> Into<U> for T where U: From<T> {
    fn into(self) -> U {
        U::from(self)
    }
}

函数如果接受一个 IpAddr 为参数,我们可以使用 Into 让更多的类型可以被这个函数使用:

fn print(val: impl Into<IpAddr>) {
    println!("{:?}", val.into());
}

那么 print([1,1,1,1]) 是可以运行的,因为 IpAddr 实现了 From<[u8; 4]>

  • 如果你的数据类型在转换过程中有可能出现错误,可以使用
    • TryFrom<T>TryInto<T>,trait 内多了一个关联类型 Error,且返回的结果是 Result<T, Self::Error>

AsRef / AsMut

引用到引用的转换:

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

pub trait AsMut<T> where T: ?Sized {
    fn as_mut(&self) -> &mut T;
}
  • 这里AsRef<T>就说明Self可以转换为&T,比如P: AsRef<Path>说明类型P是实现了可以转换为Path引用的类型,比如String, &str, PathBuf, Path类型,而且执行path.as_ref()就可以得到一个&Path

  • AsRef<T>可以看成作为T的引用&T

v.as_ref().clone()v 作引用转换(V作为U类型的引用),获得了另一个类型U的引用,再把这个引用U clone出了一个带所有权的U数据,实现上就是 From<T> for U

操作符相关:Deref / DerefMut

ops

Rust 为所有的运算符都提供了 trait,你可以为自己的类型重载某些操作符

pub trait Deref {
    type Target: ?Sized;
    fn deref(&self) -> &Self::Target;
}

pub trait DerefMut: Deref {
    type Target: ?Sized;
    fn deref_mut(&mut self) -> &mut Self::Target;
}
  • *b 会被展开为 *(b.deref())

下面这个例子:

let mut buf: Buffer = Buffer::new([3,2,1]);
buf.sort();
  • Buffer::new中实现把<T>转化成Vec<T>的功能
  • .的功能
    • 把buf中的Vec<T>转成[T]/Vec<T>/&[T]/&mut [T],即它的deref()
    • 并且.还可以deref到&mut [T]
    • 再使用[T]的sort方法

其它:Debug / Display / Default

  • Debug 是为开发者调试打印数据结构所设计的
  • Display 是给用户显示数据结构所设计的
  • Default 用于为类型提供缺省值
    • 通过派生宏来为结构体设置缺省值 #[derive(Default)]
    • 通过 Default::default() 初始化结构体缺省值

Trait 总结

to

  • trait 实现了延迟绑定
    • 数据结构是具体数据的延迟绑定
    • 泛型结构是具体数据结构的延迟绑定
    • 函数是一组实现某个功能的表达式的延迟绑定
    • 泛型函数是函数的延迟绑定
    • trait 是行为的延迟绑定