Pearl: Accept a possibly-owned String-like argument

Say we have:

struct Foo {
  string: String
}

impl Foo {
  fn new(thing: ?) -> Foo {
    let string: String = thing.magic();
    Foo { string }
  }
}

If thing is a String already, I'd like to claim ownership as-is without a clone. If it's some other kind of string-like object, I'd like to to_string() it (etc.) to produced an owned version. I'm sure there's a Trait or wrapper type in std for this, I'm just not sure what it is. ToString doesn't seem right, as that clones the String if it was already a String.

Thoughts? Thanks you kindly.

Cow<'a, str>.

use std::borrow::Cow;

fn f(s: Cow<'_, str>) {
    let s = s.into_owned();
    // You have a String now
}

Edit: Correction by @Hyeonu, thanks.

Actually it's Cow::into_owned(). .to_owned() invokes the implementation of the str by autoderef, so it always allocate.

1 Like

How about on the caller's end, though? Is there a Trait that allows Foo::new() to accept string literals as-is?

On the caller's end with Cow you need .into(). This works instead though:

fn foo<'a, S: Into<Cow<'a, str>>>(s: S) {
    let s = s.into().into_owned();
    // You have a String now
}

fn main() {
    foo("foo");
    foo("bar".to_string());
}

Which would in theory work too if they in turn had an owned String from elsewhere, and were passing it down to me as-is. Thank you, I will try this out.

The intermediate Cow isn’t necessary: &str and String both implement Into<String>:


fn foo<'a, S: Into<String>>(s: S) {
    let s = s.into();
    // You have a String now
}

fn main() {
    foo("foo");
    foo("bar".to_string());
}

5 Likes

Heh, derp. Failed to get off my thought train.

Ah ha, so no cow-tipping necessary. Okay, thanks folks! My understanding of From and Into are still a bit hazy, so I'll read up on them.

Ah looks like this does the right thing due to blanket implementations.

impl<T> From<T> for T {
    fn from(t: T) -> T {
        t
    }
}

So converting something to itself always transfers ownership and doesn't clone. Then:

impl<T, U> Into<U> for T
where
    U: From<T>,
{
    fn into(self) -> U {
        U::from(self)
    }
}

where T and U are both String, and we're all good.