Rust的ToOwned特征:泛型版的Clone

std::borrow::ToOwned是Rust标准库中的一个特征,用于从借用的数据中创建一个具有所有权的副本。它的作用和Clone是一样的,但是相比Clone,它支持泛型;也就是说我们可以将一个类型T“Clone”为另一个类型U。这对处理一些特殊的类型来说很有用。

ToOwned的签名

ToOwned提供了两个方法,其中一个是必须实现的:

pub trait ToOwned {
    type Owned: Borrow<Self>;

    // Required method
    fn to_owned(&self) -> Self::Owned;

    // Provided method
    fn clone_into(&self, target: &mut Self::Owned) { ... }
}

例子

说到例子,就不得不再次请出我文章里的常客——str[T]。众所周知,因为str[T]是切片类型,它们在编译时大小未知,因此Rust无法在栈上为它们分配空间;这就导致了我们几乎不会直接持有str[T]变量:

fn main() {
    let foo: str;
    let bar: [i32];
}

这段代码会被编译器直接报错:

str[T]是不能被持有了,但是Clone它们的需求是确实存在的;因此,Rust就提供了将这些切片类型“Clone”为栈上类型的方案:ToOwned。打开标准库文档和源码,我们可以看到str这样实现了ToOwned

#[stable(feature = "rust1", since = "1.0.0")]
impl ToOwned for str {
    type Owned = String;
    #[inline]
    fn to_owned(&self) -> String {
        unsafe { String::from_utf8_unchecked(self.as_bytes().to_owned()) }
    }

    fn clone_into(&self, target: &mut String) {
        let mut b = mem::take(target).into_bytes();
        self.as_bytes().clone_into(&mut b);
        *target = unsafe { String::from_utf8_unchecked(b) }
    }
}

也可以看到[T]这样实现了ToOwned

#[stable(feature = "rust1", since = "1.0.0")]
impl<T: Clone> ToOwned for [T] {
    type Owned = Vec<T>;
    #[cfg(not(test))]
    fn to_owned(&self) -> Vec<T> {
        self.to_vec()
    }

    #[cfg(test)]
    fn to_owned(&self) -> Vec<T> {
        hack::to_vec(self, Global)
    }

    fn clone_into(&self, target: &mut Vec<T>) {
        SpecCloneIntoVec::clone_into(self, target);
    }
}

于是,用户虽然不能直接Clonestr[T],但却可以把它们用ToOwned“Clone”为StringVec<T>


小插曲:在日常开发中,我们经常使用的方法String::from(&str),其实现正是依赖于strto_owned特征:

#[stable(feature = "rust1", since = "1.0.0")]
impl From<&str> for String {
    #[inline]
    fn from(s: &str) -> String {
        s.to_owned()
    }
}

总结

对普通的开发者来说,对ToOwned有一些了解就已经完全足够了;而对API的设计者来说,巧用ToOwned可以让你的类型在一些特殊情况下更灵活、更高效。


下篇文章介绍的是Rust中经常被人忽视的智能指针Cow,我之前在学习Rust时就注意到无论是the book,还是Rust圣经,都没有讲到Cow,希望我的工作能为Rust增加一点可供新人参考的资料。

热门相关:龙组使命   公子风流   盛宠之嫡女医妃   铁血大明   龙组使命