为用户定义类型实现 ToOwned

Spr*_*ite 10 ownership rust

考虑以下示例代码:

#[derive(Clone)]
struct DataRef<'a> {
    text: &'a str,
}

#[derive(Clone)]
struct DataOwned {
    text: String,
}
Run Code Online (Sandbox Code Playgroud)

我将以这种方式ToOwned实现:DataRef

impl ToOwned for DataRef<'_> {
    type Owned = DataOwned;

    fn to_owned(&self) -> DataOwned {
        DataOwned {
            text: self.text.to_owned(),
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

从字面上看,这句话有道理吧?但也存在一些问题。


第一个问题是,因为ToOwned提供了一个全面的实现

impl<T> ToOwned for T where T: Clone { ... }
Run Code Online (Sandbox Code Playgroud)

上面的代码会出现编译错误:

error[E0119]: conflicting implementations of trait `std::borrow::ToOwned` for type `DataRef<'_>`
  --> src/main.rs:13:1
   |
13 | impl ToOwned for DataRef<'_> {
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |
   = note: conflicting implementation in crate `alloc`:
           - impl<T> ToOwned for T
             where T: Clone;
Run Code Online (Sandbox Code Playgroud)

好吧,我们可以做出一些妥协。让我们#[derive(Clone)]从 中删除DataRef。(但是,在我的实际情况中我不能这样做,因为这是一个破坏性的改变并且没有意义)


然后是第二个问题,关联类型Owned要求ToOwned 实现Borrow<Self>

error[E0119]: conflicting implementations of trait `std::borrow::ToOwned` for type `DataRef<'_>`
  --> src/main.rs:13:1
   |
13 | impl ToOwned for DataRef<'_> {
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |
   = note: conflicting implementation in crate `alloc`:
           - impl<T> ToOwned for T
             where T: Clone;
Run Code Online (Sandbox Code Playgroud)

如果我们按照定义实现Borrowfor :DataOwned

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

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

    ...
}
Run Code Online (Sandbox Code Playgroud)

这显然是不可能的,因为我们无法将 a 存储DataRef在某个地方。


所以我的问题是:

  • ToOwned对于上面的例子,有什么办法可以实现吗?

  • 考虑一下上面的问题,是ToOwned不是应该由用户手动实现呢?因为我无法想象一个真实的例子来反对这一点。

  • (可选答案)如果允许更改 std 的定义ToOwned,是否有任何可能的改进可以使其更好?(允许不稳定和未实现的 Rust 功能)

Kev*_*eid 13

您看到的问题是因为ToOwned不应该针对引用类型实现,而是针对引用对象实现。请注意标准库实现的样子:

\n
impl ToOwned for str\nimpl ToOwned for CStr\nimpl ToOwned for OsStr\nimpl ToOwned for Path\nimpl<T> ToOwned for [T]\n
Run Code Online (Sandbox Code Playgroud)\n

这些都是!Sized, !Clone总是出现在某些通用指针类型(例如&str,,,)或专门拥有的指针类型(包含 a ;包含 a ;包含 a )内部Box<str>的所有类型。的目的是允许从对数据 \xe2\x80\x94 的引用(不是您所说的东西,而是实际的\xe2\x80\x94 )转换为专用的拥有指针类型,这样转换是可逆且一致的(这就是它的意义所在)。&PathStringstrPathBufPathVec<T>[T]ToOwnedFooRef&Borrow<Self>

\n

Borrow如果您想获得和的好处ToOwned的好处,则需要定义一个类型,该类型不是引用,但引用可以指向某些内容,如下所示:

\n
use std::borrow::Borrow;\n#[repr(transparent)]\nstruct Data {\n    text: str,\n}\n\n#[derive(Clone)]\nstruct DataOwned {\n    text: String,\n}\n\nimpl Borrow<Data> for DataOwned {\n    fn borrow<\'s>(&\'s self) -> &\'s Data {\n        // Use unsafe code to change type of referent.\n        // Safety: `Data` is a `repr(transparent)` wrapper around `str`.\n        let ptr = &*self.text as *const str as *const Data;\n        unsafe { &*ptr }\n    }\n}\n\nimpl ToOwned for Data {\n    type Owned = DataOwned;\n    fn to_owned(&self) -> DataOwned {\n        DataOwned { text: String::from(&self.text) }\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

但请注意,此策略适用于单个连续数据块(例如 a 中的 UTF-8 字节str)。没有办法以适用于包含两个字符串的a 的方式来实现Borrow+ 。(这可以针对一个字符串和一些固定大小的数据完成,但这仍然具有挑战性,因为 Rust 尚未很好地支持自定义动态大小的类型。)ToOwnedDataOwned

\n

通常不值得为String包装器执行所有这些操作,但如果您想对内容强制执行一些更强的类型/有效性不变性(例如“所有字符都是 ASCII”或“字符串(或字符串切片)”,则可能是值得的)是一个格式良好的 JSON 片段”),并且您希望能够与期望ToOwned实现的现有通用代码进行交互。

\n

如果您只想能够调用.to_owned()a 上的方法DataRef,请不要理会该ToOwned特征;只需编写一个固有(非特征)方法即可。

\n