Going beyond AsRef/Into

struct Person {
    name: String
}

impl Person {
    fn set_name(&mut self, name: ???) {
        self.name = name;
    }
}

Let's say you want to write the set_name function to most efficiently re-use allocations, for both borrowed and owned source values. It should handle these two cases:

  1. If the caller passes a borrowed value (&str), we should preserve the existing buffer in self.name instead of throwing it away.
  2. If the caller passes an owned value (String), we should store that String directly to avoid a memcpy, and drop the previous value.

What type should set_name's parameter have? Using Into<String> fails case #1, since it allocates a new String. Using AsRef<str> fails case #2, since you can't access the backing String.

You could use Into<Cow<str>>:

    fn set_name<'a>(&mut self, name: impl Into<Cow<'a, str>>) {
        match name.into() {
            Cow::Borrowed(s) => {
                self.name.clear();
                self.name.push_str(s);
            }
            Cow::Owned(s) => {
                self.name = s;
            }
        }
    }

But it's very fiddly and 9 lines long, so nobody ever bothers (and I don't blame them). And that's assuming the compiler can unwind all that extra abstraction in every situation.

Is there any better pattern that handles both cases easily and is more likely to be widely adopted?

Note that a memcpy() can easily be faster than a deallocation for typical string sizes, especially if you happen to be using a more complex memory allocator. Thus, I would write this as

impl Person {
    fn set_name(&mut self, name: &str) {
        self.name.clear();
        self.name.push_str(name);
    }
}

If you still absolutely want to drop the previous string and use the allocation of the new one, you can create an extension trait:

trait CopyToString {
    fn copy_to_string(self, string: &mut String);
}

impl CopyToString for String {
    fn copy_to_string(self, string: &mut String) {
        *string = self;
    }
}

impl CopyToString for &'_ str {
    fn copy_to_string(self, string: &mut String) {
        string.clear();
        string.push_str(self);
    }
}
1 Like

I guess you could use something like deref_owned::GenericCow. There has been some arguments about the usefulness (and particular implementation), however. (In particular in regard to the Deref supertrait, which might have been a bad idea.) I still believe something like that is missing in Rust, though.

P.S.: Not sure if you would have the noise of the Owned wrapper then though.

If we follow @H2CO3's advice of preferring copying, then ToOwned::clone_into() is a way to execute the replacement that works generically (i.e. not needing String-specific functions, but working on anything that implements ToOwned or Clone):

struct Person {
    name: String
}

impl Person {
    fn set_name<N: ?Sized + ToOwned<Owned = String>>(&mut self, name: &N) {
        name.clone_into(&mut self.name);
    }
}

fn main() {
    let mut p = Person { name: String::new() };
    
    p.set_name("Alice");
    p.set_name(&String::from("Bob"));
}
2 Likes

I was thinking of Clone::clone_from() but quickly abandoned the thought since that would require the RHS to be exactly T, not Borrow<T>. It's neat that ToOwned (now) has clone_into().

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.