通用的可克隆/可移动参数作为函数参数

4

对于任意实现了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`

如果我尝试为&MyStruct实现ToOwned
impl<'a> ToOwned for &'a MyStruct {
    type Owned = MyStruct;

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

I get the following error:

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`

2
你已经熟悉 Cow 了吗? - Shepmaster
哦,我不知道Cow..这正是我在寻找的,一定是错过了。虽然,在某些情况下,将输入包装在枚举类型中似乎是不必要的(即Cow与动态分派相对应,就像我的TraitInQuestion与静态分派相对应一样,如果这个比喻说得通的话)。 - 10101
3个回答

2
如@Shepmaster所指出的那样,有Cow; 但是您需要手动创建Cow :: Borrowed(&a)Cow :: Owned(a)实例,并且包装(Owned)类型必须始终实现Clone(对于T:ToOwned<Owned=T>)。
(对于自定义ToOwned实现,ToOwned :: OwnedClone可能并不是严格必要的;但是。borrow().to_owned()就像.clone()一样,因此没有隐藏它的理由。)
您自己的特征是替代方案的良好起点,尽管您应该使用通用实现。 只要不传递引用,您就不需要一个类型来实现ClonePlayground
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<&_> 的冲突实现,类型为 &_"(有些疯狂的人可能会为 Foo 实现 ToOwned { type Owned = &'static Foo; ... }; 特质无法根据生命周期差异区分实现)。
但是与 ToOwned 类似,您可以实现特定的自定义,例如:
impl<'a> CloneOrNoOp<String> for &'a str {
    fn clone_or_no_op(self) -> String {
        self.to_owned()
    }
}

现在,如果您想获得一个字符串(`String`),可以传递任何 `&str`、`&String` 或 `String` 中的任意一个参数:
test2::<String,_>("abc");
test2::<String,_>(&String::from("abc"));
test2::<String,_>(String::from("abc"));

0

您可以使用Into特质来获取您想要的内容(playground)。

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

由于标准库中已经存在 impl Into<T> for T,因此您只需要提供一个 impl Into<MyStruct> for &'_ MyStruct,这很简单,因为 MyStructClone

impl Into<MyStruct> for &'_ MyStruct {
    fn into(self) -> MyStruct {
        self.clone()
    }
}

fn test(t: impl Into<MyStruct>) {
    let owned = t.into();
}

作为奖励,如果您想将 t 用作引用,并且有时只克隆它,则可以将 AsRef<MyStrcut> 添加到混合中:
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();
    }
}

0

你可以通过实现自己的通用特性来完成。我使用的特性有两个函数:

  • 用于移动/克隆值;
  • 用于获取引用。 因此,你可以编写一个函数,该函数可以移动或引用参数,并在需要时获取引用并移动/克隆它。

这个特性可以为任何类型为T的移动值和任何类型为&T的引用值进行实现。因此,它可以很容易地作为一个库与任何实现了Clone的类型一起使用。特性需要通过目标类型T进行参数化,以便为两种情况都实现它。

特性实现:

trait RefOrValue<T> {
    fn as_value(self) -> T;
    fn as_ref(&self) -> &T;
}

impl<T> RefOrValue<T> for T {
    fn as_value(self) -> T {
        self
    }

    fn as_ref(&self) -> &T {
        self
    }
}

impl<T: Clone> RefOrValue<T> for &T {
    fn as_value(self) -> T {
        self.clone()
    }

    fn as_ref(&self) -> &T {
        *self
    }
}

使用 RefOrValue 特质重写您的示例:

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 }
    }
}

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

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

Rust 演示链接


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接