Rust-Cow智能指针

  1. 一、介绍
  2. 二、示例
    1. 观察所有权
      1. 借用修改取出所有权
      2. 在所有权数据上进行修改
  • 三、代码
    1. 相关 trait
    2. 方法
      1. to_mut()
      2. into_owned()
      3. deref
  • 一、介绍

    它的定义是 Clone-on-write,即写时克隆。本质上是一个智能指针。在数据需要修改或者所有权发生变化时使用,多用于读多写少的场景。

    pub enum Cow<'a, B: ?Sized + 'a>
    where
    B: ToOwned,
    {
    /// 用于包裹对象的引用(通用引用);
    Borrowed(&'a B),

    /// 用于包裹对象的所有者;
    Owned(<B as ToOwned>::Owned),
    }

    数据在写入的情况下 Cow 才有存在的意义。当借用的数据被修改时,在不破坏原有数据的情况下,克隆一个副本并且在副本上进行修改;这是一种惰性的策略,在真正需要修改时才产生克隆的操作,而并不预先克隆。

    关键函数

    • to_mut(): 获取所有权数据的可变引用,无所有权时从借用数据中克隆
    • into_owned(): 提取所有权数据。

    它有以下几个要点需要掌握:

    1. Cow<T> 能直接调用 T 的不可变方法,因为 Cow 这个枚举,实现了 Deref;
    2. 在需要写 T 的时候,可以使用 .to_mut() 方法得到一个具有所有权的值的可变借用;
      1. 注意,调用 .to_mut() 不一定会产生克隆;
      2. 在已经具有所有权的情况下,调用 .to_mut() 有效,但是不会产生新的克隆;
      3. 多次调用 .to_mut() 只会产生一次克隆。
    3. 在需要写 T 的时候,可以使用 .into_owned() 创建新的拥有所有权的对象,这个过程往往意味着内存拷贝并创建新对象;
      1. 如果之前 Cow 中的值是借用状态,调用此操作将执行克隆;
      2. 本方法,参数是self类型,它会“吃掉”原先的那个对象,调用之后原先的对象的生命周期就截止了,在 Cow 上不能调用多次;

    二、示例

    官方描述了三种情况

    1. 借用数据,但是未调用 to_mut(),故不存在 clone 操作
    2. 借用数据,调用 to_mut(), 发生 clone 操作
    3. 所有权数据,调用 to_mut(), 不存在 clone 操作,因为具有该数据的所有权
    use std::borrow::Cow;

    fn abs_all(input: &mut Cow<[i32]>) {
    for i in 0..input.len() {
    let v = input[i];
    if v < 0 {
    // Clones into a vector if not already owned.
    input.to_mut()[i] = -v;
    }
    }
    }

    // No clone occurs because `input` doesn't need to be mutated.
    let slice = [0, 1, 2];
    let mut input = Cow::from(&slice[..]);
    abs_all(&mut input);

    // Clone occurs because `input` needs to be mutated.
    let slice = [-1, 0, 1];
    let mut input = Cow::from(&slice[..]);
    abs_all(&mut input);

    // No clone occurs because `input` is already owned.
    let mut input = Cow::from(vec![-1, 0, 1]);
    abs_all(&mut input);

    观察所有权

    写了个地址打印函数,以此来观察所有权的变化。

    fn print_addr(s: &str) {
    println!("{}", s);
    let mut p = s.as_ptr();
    for ch in s.chars() {
    println!("\t{:p}\t{}", p, ch);
    p = p.wrapping_add(ch.len_utf8());
    }
    }

    借用修改取出所有权

    对借用的数据进行修改操作(有可能不会修改,见下文),操作完成后取出所有权 是最常见的用法

    以下是一般的借用数据从修改至获取所有权数据的过程,通过新产生的地址可以看出来存在 clone 操作

    {
    let s = String::from("AB");
    print_addr(&s);
    let mut cow = Cow::Borrowed(&s);
    cow.to_mut().insert_str(1, "cd");
    let sr = cow.into_owned();
    print_addr(&sr);
    }

    // AB
    // 0x7fb694c05af0 A
    // 0x7fb694c05af1 B
    // AcdB
    // 0x7fb694c05b00 A
    // 0x7fb694c05b01 c
    // 0x7fb694c05b02 d
    // 0x7fb694c05b03 B

    将上面的代码注释 to_mut() 行后,相当于不会有获取所有权的需求,这个时候是不应该做修改的,into_owned() 应该弃用转而使用 as_str() 这类没有所有权变化的操作.

    {
    let s = String::from("AB");
    print_addr(&s);
    let mut cow = Cow::Borrowed(&s);
    // cow.to_mut().insert_str(1, "cd"); // 这一行是运行时决定的
    let sr = cow.as_str(); // 看后续的使用,若是后续也只是读操作可以使用 as_str()
    print_addr(&sr);
    }

    在所有权数据上进行修改

    在对已具有所有权数据上操作时,字符串的地址未发生改变,未发生 clone 操作

    insert_str 为两个 memcpy 操作,故首地址不会发生变化

    {
    let s1 = String::from("cd");
    print_addr(&s1);
    let mut cow1: Cow<'_, String> = Cow::Owned(s1);
    cow1.to_mut().insert_str(0, "AB");
    let sr1 = cow1.into_owned();
    print_addr(&sr1);
    }

    // cd
    // 0x7fb694c05b10 c
    // 0x7fb694c05b11 d
    // ABcd
    // 0x7fb694c05b10 A
    // 0x7fb694c05b11 B
    // 0x7fb694c05b12 c
    // 0x7fb694c05b13 d

    三、代码

    Cow 是一个枚举值,包含了一个借用和所有。可以使用 Cow 的类型需要实现了 ToOwned trait。

    ToOwned trait 同样包含了所有权或借用的操作。

    1. 需要实现 Borrow 借用 trait
    2. 可以从借用的数据中创建所有权数据或者克隆

    相关 trait

    pub trait ToOwned {
    type Owned: Borrow<Self>; // 需要实现 Borrow triat

    pub fn to_owned(&self) -> Self::Owned; // 所有权创建
    pub fn clone_into(&self, target: &mut Self::Owned) { ... }
    }

    pub trait Borrow<Borrowed> where
    Borrowed: ?Sized, {
    pub fn borrow(&self) -> &Borrowed;
    }

    Borrow 借用 triat 泛型实现

    impl<T: ?Sized> Borrow<T> for T {
    fn borrow(&self) -> &T {
    self
    }
    }

    to_owned 创建所有权的泛型实现如下,取决该类型是否实现 Clone tait
    impl<T> ToOwned for T
    where
    T: Clone,
    {
    type Owned = T;
    fn to_owned(&self) -> T {
    self.clone()
    }
    }

    方法

    to_mut()

    获取所有权的可变引用

    已获取所有权直接返回引用
    借用数据的情况先调用 to_owned() 获取克隆副本的所有权,并且做一个检查
    ref 关键字指示模式匹配为借用而不是移动。
    <B as ToOwned>::Owned 表示 B 类型实现了 ToOwned trait,现使用该 trait 中的 Owned 类型,本质就是B类型本身,但是限制了实现 trait

    pub fn to_mut(&mut self) -> &mut <B as ToOwned>::Owned {
    match *self {
    Borrowed(borrowed) => {
    *self = Owned(borrowed.to_owned());
    match *self {
    Borrowed(..) => unreachable!(),
    Owned(ref mut owned) => owned,
    }
    }
    Owned(ref mut owned) => owned,
    }
    }

    into_owned()

    取出 Cow 中的所有权数据,当为获取所有权时,进行 clone 操作

    pub fn into_owned(self) -> <B as ToOwned>::Owned {
    match self {
    Borrowed(borrowed) => borrowed.to_owned(),
    Owned(owned) => owned,
    }
    }

    deref

    由于 Cow 也实现了 Deref trait, 支持解引用强制多态,实现如下。

    impl<B: ?Sized + ToOwned> Deref for Cow<'_, B> {
    type Target = B;

    fn deref(&self) -> &B {
    match *self {
    Borrowed(borrowed) => borrowed,
    Owned(ref owned) => owned.borrow(),
    }
    }
    }