Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Generic cloneable/movable parameter as function argument

Tags:

generics

rust

For an arbitrary struct that implements Clone, I would like to have a single generic function that takes either:

  • a &MyStruct in which case it can be conditionally cloned by the function
  • a MyStruct in which case the clone is unnecessary since it can be moved

I've implemented this on my own:

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

and the output is as expected:

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

Is this functionality already derived somehow by implementing Clone? If not, std::borrow::ToOwned sounds like what I want, but I can't get it to work:

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

Compiler output:

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`

Doing what the compiler suggests by changing test:

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

And the resulting error:

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

If I try to implement ToOwned for &MyStruct

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`
like image 227
10101 Avatar asked Sep 15 '25 02:09

10101


1 Answers

As @Shepmaster pointed out there is Cow; but you'd need to manually create the Cow::Borrowed(&a) or Cow::Owned(a) instances, and the wrapped (Owned) type must always implement Clone (for T: ToOwned<Owned=T>).

(The Clone for ToOwned::Owned might not be strictly necessary with custom ToOwned implementations; but .borrow().to_owned() acts like a .clone(), so there is no reason to hide it.)

Your own trait is a nice start for an alternative, although you should use generic implementations. That way you don't require a type to implement Clone as long as you don't pass a reference:

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

Sadly it seems impossible for now to base CloneOrNoOp on ToOwned (instead of Clone) like this:

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

The compiler sees conflicting implementations of "CloneOrNoOp<&_> for type &_" (someone crazy might implement ToOwned for Foo { type Owned = &'static Foo; ... }; traits can't distinguish implementations based on lifetime differences).

But similar to ToOwned you can implement specific customizations, like:

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

Now you can pass any of &str, &String or String if you want to get a String:

test2::<String,_>("abc");
test2::<String,_>(&String::from("abc"));
test2::<String,_>(String::from("abc"));
like image 137
Stefan Avatar answered Sep 16 '25 22:09

Stefan