Consumed param that can be owned or a ref

I would like to make a function that can take either owned value, or a reference that can be cloned into an owned value. This way an owned value can be passed in without extra cloning. It seems I am able to do it with a custom trait specifically for String/&str, but I couldn't find anything similar/generic in stdlib. Am this approach incorrect?

trait MyToString {
    fn to_owned_string(self) -> String;
}

impl MyToString for &str {
    fn to_owned_string(self) -> String {
        self.to_string()
    }
}

impl MyToString for String {
    fn to_owned_string(self) -> String {
        self
    }
}

fn foo<T: MyToString>(v: T) -> String {
    v.to_owned_string()
}

fn main() {
    let s_ref: &str = "hello";
    println!("{}", foo(s_ref));

    let s_owned: String = String::from("world");
    println!("{}", foo(s_owned));
}

You can use a Cow (Clone-on-write), which allows the value to be either Owned or Borrowed. Cow in std::borrow - Rust

@zacryol the issue is ergonomics -- I would like to create a function (foo above) that simply accepts a generic that can be either owned or borrowed, and clone it if needed. With Cow, my understanding is that each caller would have to do foo(Cow::from(...))

You can accept both with fn foo<'a, T: Into<Cow<'a, str>>>(v: T).

fn foo<'a, T: Into<Cow<'a, str>>>(v: T) -> String {
    let cow: Cow<'a, str> = v.into();
    cow.into_owned()
}

If you simply want to be able to accept both String and &str and always convert &str to an owned string just use Into:

fn foo<T: Into<String>>(v: T) -> String {
    v.into()
}
4 Likes

The OP wanted something generic, not String specific. Here's the Cow version implemented generically:

fn foo<'a, T: Into<Cow<'a, U>>, U: ?Sized + ToOwned + 'a>(v: T) -> U::Owned {
    let cow: Cow<'a, U> = v.into();
    cow.into_owned()
}

It works ok for &str, String, &Path, PathBuf, &[T], Vec<T>... the things related to ToOwned implementations in std. But note that for all of these, there's are explicit From<X> for Cow<'a, Y> implementations, not a blanket one:

impl<'a> From<&'a str> for Cow<'a, str> ...
impl<'a> From<&'a String> for Cow<'a, str> ...
impl<'a> From<String> for Cow<'a, str> ...

So these don't work on their own:

#[derive(Clone, Debug)]
struct My;

    let m = foo(&My);
    println!("m: {} = {m:?}", std::any::type_name_of_val(&m));
    let mo = foo(My);
    println!("mo: {} = {mo:?}", std::any::type_name_of_val(&mo));

You also need these:

impl<'a> From<&'a My> for Cow<'a, My> {
    fn from(i: &'a My) -> Self {
        Self::Borrowed(i)
    }
}

impl From<My> for Cow<'_, My> {
    fn from(m: My) -> Self {
        Self::Owned(m)
    }
}

But this or a home-baked version of something like this may be the best you can get.


Here's the generic version of the Into approach:

fn quz<T: Into<U>, U>(v: T) -> U {
    v.into()
}

And all the std types above are still covered. But it's more ambiguous, because every T: Into<T>.[1] So you have to add annotations when the desired type isn't inferred from the context of the call site, which probably makes this a non-starter.

It also requires

impl From<&My> for My ...

as Clone doesn't give you that automatically, either.


  1. In contrast, there is no From<String> for Cow<'_, String>, for example. ↩︎

2 Likes

Thanks for all amazing responses!

As a general practice, does it make sense for public APIs to use impl Into<String> instead of String?

fn foo(v: impl Into<String>) -> String {
    v.into()
}

If you're always going to call into to produce a String, it's perfectly reasonable in my opinion. That is to say, it offers some ergonomics to consumers without introducing a cost, such as an unnecessary clone.[1]

fn foo<S: Into<String>>(v: S) -> String { ... }

means the same thing but allows turbo-fishing, if you want to be maximally accommodating to consumers.


  1. Potential extra monomorphizing is considered a downside in some niche areas, but that doesn't seem to be your concern. ↩︎

5 Likes

As a counterpoint, I rarely bother with Into in the public APIs that I write; I prefer that unconditional conversions happen explicitly on the client side, because that may have performance implications. That said, I agree that it’s a perfectly reasonable choice, and I wouldn’t get upset if I saw it in some library I wanted to use— This is one of those things that comes down to personal programming style.

3 Likes

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.