【问题标题】:Generic cloneable/movable parameter as function argument通用可克隆/可移动参数作为函数参数
【发布时间】:2017-11-12 21:04:57
【问题描述】:

对于实现Clone 的任意结构,我希望有一个通用函数,它采用以下任一方式:

  • &MyStruct 在这种情况下,它可以被函数有条件地克隆
  • MyStruct 在这种情况下克隆是不必要的,因为它可以移动

我自己实现了这个:

use std::clone::Clone;

#[derive(Debug)]
struct MyStruct {
    value: u64,
}

impl Clone for MyStruct {
    fn clone(&self) -> Self {
        println!("cloning {:?}", self);
        MyStruct { value: self.value }
    }
}

trait TraitInQuestion<T> {
    fn clone_or_no_op(self) -> T;
}

impl TraitInQuestion<MyStruct> for MyStruct {
    fn clone_or_no_op(self) -> MyStruct {
        self
    }
}

impl<'a> TraitInQuestion<MyStruct> for &'a MyStruct {
    fn clone_or_no_op(self) -> MyStruct {
        self.clone()
    }
}

fn test<T: TraitInQuestion<MyStruct>>(t: T) {
    let owned = t.clone_or_no_op();
}

fn main() {
    let a = MyStruct { value: 8675309 };

    println!("borrowing to be cloned");
    test(&a);

    println!("moving");
    test(a);
}

输出如预期:

borrowing to be cloned
cloning MyStruct { value: 8675309 }
moving

这个功能是否已经通过实现Clone 派生出来了?如果没有,std::borrow::ToOwned 听起来像我想要的,但我无法让它工作:

use std::clone::Clone;
use std::borrow::Borrow;

#[derive(Debug)]
struct MyStruct {
    value: u64,
}

impl Clone for MyStruct {
    fn clone(&self) -> Self {
        println!("cloning {:?}", self);
        MyStruct { value: self.value }
    }
}

fn test<T: ToOwned<Owned = MyStruct>>(a: T) {
    let owned = a.to_owned();
}

fn main() {
    let a = MyStruct { value: 8675309 };

    println!("borrowing to be cloned");
    test(&a);

    println!("moving");
    test(a);
}

编译器输出:

error[E0277]: the trait bound `MyStruct: std::borrow::Borrow<T>` is not satisfied
  --> src/main.rs:16:1
   |
16 | / fn test<T: ToOwned<Owned = MyStruct>>(a: T) {
17 | |     let owned = a.to_owned();
18 | | }
   | |_^ the trait `std::borrow::Borrow<T>` is not implemented for `MyStruct`
   |
   = help: consider adding a `where MyStruct: std::borrow::Borrow<T>` bound
   = note: required by `std::borrow::ToOwned`

按照编译器的建议通过更改test

fn test<T: ToOwned<Owned = MyStruct>>(a: T) -> ()
where
    MyStruct: Borrow<T>,
{
    let owned = a.to_owned();
}

以及由此产生的错误:

error[E0308]: mismatched types
  --> src/main.rs:27:10
   |
27 |     test(&a);
   |          ^^ expected struct `MyStruct`, found &MyStruct
   |
   = note: expected type `MyStruct`
              found type `&MyStruct`

如果我尝试为&amp;MyStruct 实现ToOwned

impl<'a> ToOwned for &'a MyStruct {
    type Owned = MyStruct;

    fn to_owned(&self) -> Self::Owned {
        self.clone()
    }
}

我收到以下错误:

error[E0119]: conflicting implementations of trait `std::borrow::ToOwned` for type `&MyStruct`:
  --> src/main.rs:16:1
   |
16 | / impl<'a> ToOwned for &'a MyStruct {
17 | |     type Owned = MyStruct;
18 | |
19 | |     fn to_owned(&self) -> Self::Owned {
20 | |         self.clone()
21 | |     }
22 | | }
   | |_^
   |
   = note: conflicting implementation in crate `alloc`

【问题讨论】:

标签: generics rust


【解决方案1】:

正如@Shepmaster 指出的那样,有Cow;但是您需要手动创建 Cow::Borrowed(&amp;a)Cow::Owned(a) 实例,并且包装的 (Owned) 类型必须始终实现 Clone(对于 T: ToOwned&lt;Owned=T&gt;)。

ToOwned::OwnedClone 对于自定义ToOwned 实现可能不是绝对必要的;但.borrow().to_owned() 的作用类似于.clone(),因此没有理由隐藏它。)

您自己的 trait 是替代方案的良好开端,尽管您应该使用通用实现。这样一来,只要不传递引用,您就不需要类型来实现 Clone

Playground

trait CloneOrNoOp<T> {
    fn clone_or_no_op(self) -> T;
}

impl<T> CloneOrNoOp<T> for T {
    fn clone_or_no_op(self) -> T {
        self
    }
}

impl<'a, T: Clone> CloneOrNoOp<T> for &'a T {
    fn clone_or_no_op(self) -> T {
        self.clone()
    }
}

struct MyStructNoClone;

#[derive(Debug)]
struct MyStruct {
    value: u64,
}

impl Clone for MyStruct {
    fn clone(&self) -> Self {
        println!("cloning {:?}", self);
        MyStruct { value: self.value }
    }
}

fn test<T: CloneOrNoOp<MyStruct>>(t: T) {
    let _owned = t.clone_or_no_op();
}

// if `I` implement `Clone` this takes either `&I` or `I`; if `I` doesn't
// implement `Clone` it still will accept `I` (but not `&I`).
fn test2<I, T: CloneOrNoOp<I>>(t: T) {
    let _owned: I = t.clone_or_no_op();
}

fn main() {
    let a = MyStruct { value: 8675309 };

    println!("borrowing to be cloned");
    test(&a);
    // cannot infer `I`, could be `&MyStruct` or `MyStruct`:
    // test2(&a);
    test2::<MyStruct,_>(&a);
    test2::<&MyStruct,_>(&a);

    println!("moving");
    test(a);

    let a = MyStructNoClone;
    test2(&a);
    // the previous line is inferred as ("cloning" the reference):
    test2::<&MyStructNoClone,_>(&a);
    // not going to work (because it can't clone):
    // test2::<MyStructNoClone,_>(&a);

    test2(a);
}

遗憾的是,现在似乎不可能像这样将CloneOrNoOp 建立在ToOwned(而不是Clone)上:

impl<'a, B> CloneOrNoOp<B::Owned> for &'a B
where
    B: ToOwned,
{
    fn clone_or_no_op(self) -> B::Owned {
        self.to_owned()
    }
}

编译器发现“CloneOrNoOp&lt;&amp;_&gt; for type &amp;_”的实现存在冲突(有些疯狂的人可能会实现 ToOwned for Foo { type Owned = &amp;'static Foo; ... };特征无法根据生命周期差异区分实现)。

但类似于ToOwned,您可以实现特定的自定义,例如:

impl<'a> CloneOrNoOp<String> for &'a str {
    fn clone_or_no_op(self) -> String {
        self.to_owned()
    }
}

如果你想得到String,现在你可以通过&amp;str&amp;StringString中的任何一个:

test2::<String,_>("abc");
test2::<String,_>(&String::from("abc"));
test2::<String,_>(String::from("abc"));

【讨论】:

    【解决方案2】:

    您可以使用 Into 特征来获得您想要的 (playground)。

    fn test(t: impl Into<MyStruct>) { ... }
    

    因为impl Into&lt;T&gt; for T已经存在于标准库中,你只需要提供一个impl Into&lt;MyStruct&gt; for &amp;'_ MyStruct,这很简单,MyStruct就是Clone

    impl Into<MyStruct> for &'_ MyStruct {
        fn into(self) -> MyStruct {
            self.clone()
        }
    }
    
    fn test(t: impl Into<MyStruct>) {
        let owned = t.into();
    }
    

    作为奖励,如果您想使用 t 作为参考并且只是偶尔克隆它,您可以将 AsRef&lt;MyStrcut&gt; 添加到组合中:

    impl AsRef<MyStruct> for MyStruct {
        #[inline]
        fn as_ref(&self) -> &MyStruct {
            self
        }
    }
    
    fn test(t: impl Into<MyStruct> + AsRef<MyStruct>) {
        let t_ref = t.as_ref();
    
        if t_ref.value > 3 {
            let owned = t.into();
        }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-05-29
      • 2015-09-22
      • 1970-01-01
      • 2014-10-24
      • 2014-10-04
      • 1970-01-01
      相关资源
      最近更新 更多