44

I want to write a single function, that accepts a &str, a String and a borrowed &String. I've written the following 2 functions:

fn accept_str_and_ref_string(value: &str) {
    println!("value: {}", value);
}

fn accept_str_and_string<S: Into<String>>(value: S) {
    let string_value: String = value.into();
    println!("string_value: {}", string_value);
}

fn main() {
    let str_foo = "foo";
    let string_foo = String::from("foo");

    accept_str_and_ref_string(str_foo);
    accept_str_and_ref_string(&string_foo);

    accept_str_and_string(str_foo);
    accept_str_and_string(string_foo);
}

Is it possible to implement one function so that I can do this:

accept_all_strings(str_foo);
accept_all_strings(&string_foo);
accept_all_strings(string_foo);
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Ringo Leese
  • 675
  • 1
  • 5
  • 6
  • 2
    It looks like since Rust 1.35.0 the `Into` variant also works for [`&String`](https://doc.rust-lang.org/std/string/struct.String.html#impl-From%3C%26%27_%20String%3E). – Matthias Jul 07 '19 at 17:57
  • Yes, you're right, I've testet it with the current 1.36 version of Rust. – Ringo Leese Jul 08 '19 at 17:36

3 Answers3

62

You can use the AsRef<str> trait:

// will accept any object that implements AsRef<str>
fn print<S: AsRef<str>>(stringlike: S) {
    // call as_ref() to get a &str
    let str_ref = stringlike.as_ref();

    println!("got: {:?}", str_ref)
}

fn main() {
    let a: &str = "str";
    let b: String = String::from("String");
    let c: &String = &b;

    print(a);
    print(c);
    print(b);
}

The print function will support any type that implements AsRef<str>, which includes &str, String and &String.

Frxstrem
  • 38,761
  • 9
  • 79
  • 119
  • @trentcl A `String` will not be copied, the ownership of it will be passed to the method unless you are using a reference. – Frxstrem Mar 09 '19 at 16:05
  • 1
    My mistake, my brain mashed up the body of the OP's example with the signature of yours. – trent Mar 09 '19 at 16:32
  • 1
    What if the function eventually needs an owned string rather than a reference? – malthe Dec 30 '19 at 10:06
  • 8
    @malthe Personally I typically just use `stringlike.as_ref().to_string()`, as I like having the same signature regardless of whether I need an owned or borrowed string. I think that might be a bit inefficient with arguments that are already owned strings, so if you want to avoid that you can use other traits, like `S: Into`. – Frxstrem Dec 30 '19 at 13:23
9

Since the 2015 rust edition the accept_all_strings function works as required by the OP with the Into<String> trait:

fn accept_all_strings<S: Into<String>>(value: S) {
    let string_value = value.into();
    println!("string_value: {string_value}");
}

Thanks to Jeremy Chone for the tip. The function signature can now be shortened like this:

fn accept_all_strings(value: impl Into<String>) {
    let string_value = value.into();
    println!("string_value: {string_value}");
}

Playground

A really good overview of conversions between String, &str, Vec<u8> and &[u8] can be found here…

Kaplan
  • 2,572
  • 13
  • 14
  • Does exactly what the OP wanted: [Playground](https://play.rust-lang.org/?version=stable&mode=release&edition=2021&gist=f0f56b4994b5c5c38b2e99f6534ee989) – so WTF? – Kaplan Sep 30 '22 at 18:36
  • 1
    Yes, this function accepts the 3 types of strings and does allocation only when it has to. It seems OP had put this option `accept_str_and_string` and perhaps thought you could not pass a &String. Btw, now, we can do `print(stringlike: impl Into) { ... }` which is a little shorter. – Jeremy Chone Nov 15 '22 at 05:07
  • @Chone You mention the new ability of the `print` macros to capture the argument directly from a variable? I took the OP's request literally that he wants the `accept_all_strings` function. IMHO: To name an user function `print` is misleading and error prone. And I'm not sure if choosing `Into` over `AsRef` is just a matter of taste… – Kaplan Nov 15 '22 at 07:04
  • Sorry, my mistake, I did not mean to refer to the `print` macro. Should be `accept_all_strings(val: impl Into)` as you noted. – Jeremy Chone Nov 15 '22 at 16:52
  • I think there is a difference between AsRef, and Into. The former can only get a reference; you must do a new allocation if you want ownership. The Into will give you ownership without allocation when possible. I tend to use the Into for my builders/struct, as most of the time, I want ownership of the String, but do not want to require the caller to add .to_string() on refs – Jeremy Chone Nov 15 '22 at 16:55
  • But `value.into()` will create/allocate a new String even if the function got a `&str`, right? (I guess it is unnecessary) – DimanNe Jan 01 '23 at 01:06
  • @DimanNe Besides printing an unmodified string, do you know of a second use case, where you don't need the string as owned? – Kaplan Jan 01 '23 at 09:22
  • @Kaplan, Yes, if I want to parse / find something in the input str/String, and return just that number/part. – DimanNe Jan 01 '23 at 12:09
0

I usually use: impl ToString.

In the docs there are a lot of things that implement that trait including str, u8, etc.

It would also let the caller pass anything that implements std::fmt::Display.

fn accept_all_strings(value: impl ToString) {
    let string_value = value.to_string();
    println!("string_value: {string_value}");
}

If you call .to_string(), it will allocate a new string which could have performance consequences. Even String::to_string() calls .to_owned(), which calls .clone().

But on the plus side, you fully own the output. No sharing.


If you just want to print it, and don't actually need an owned string value, you could use:

fn accept_all_strings(value: impl std::fmt::Display) {
    println!("value: {value}");
}

If you want to be kinder to callers, you could just require Debug (which is quite common) and use :#? to pretty print it. I guess this is just a side tip:

fn accept_all_strings(value: impl Debug) {
    println!("value: {value:#?}");
}
matiu
  • 7,469
  • 4
  • 44
  • 48