Auto .to_string on &str


#1

== edit

I am passing a label to nodes of a graph. Some names are known at compile time (i.e. the “…”.to_string()). Others are generated at runtime (concatting other names / numbers). I don’t think the later ones can be repalced with &str, and I really do need String in this case.

== orig

For a function that takes a String as an argument, is it a way to tell it to “auto .to_string” any &str arguments?

When writing a literal “foobar”, the .to_string() breaks the flow of the arguments, makes the line longer, and in general, less pleasant to read.


#2

In fact, the opposite is possible (well, not exactly opposite, but close): &String is authomatically dereferenced into &str. Doing the reverse won’t be automatic in any case, I afraid, because the memory allocation (during creation of String or any other owned object) isn’t really something that should be done implicitly “behind the scenes”, possibly unnoticed by the programmer.

If the function in question are created by you, I’d suggest to change it’s argument type to &str.


#3

It is possible, with the Into-trait.

fn foo<I: Into<String>>(foo: I) -> String {
    let mut a = foo.into();
    a.push('b');
    a
}

fn main() {
    println!("{}", foo("a"));
}

But please note, that in most of the cases a String as parameter is avoidable. In most of the cases you can accept a &str and all is fine. The same goes for Vec<T> vs &[T]. Further reading https://hermanradtke.com/2015/05/03/string-vs-str-in-rust-functions.html


#4

I think this is too strong. It’s fine to take String as an argument, provided you can consume it. If you’re going to consume it, or pass it on to something that will consume it, taking a String will save an allocation.


#5

Replying to the edit:

Well, so the question is - do you really need to pass the String, i.e. the owned value, and not &String, the reference to it (as I said before, the latter case would fit into function receiving &str)? Does this string need to be consumed here, or just referenced? Or, perhaps, you could clone it on the receiving side (i.e. move ...to_string() into the graph)?


#6

Don’t forget about the humble CoW.
This can hold either the 'static reference, or an allocated string, deferring your decision to runtime.
This might be relevant, depending on what your code does with the label.

Another good Radtke Post on this:
https://hermanradtke.com/2015/05/29/creating-a-rust-function-that-returns-string-or-str.html


#7

The Into<String> version doesn’t work with &String, because of the missing implementation of Into<String> for &String. It is possible though with a custom trait:

pub trait IntoString {
    fn into(self) -> String;
}

impl IntoString for &String {
    fn into(self) -> String {
        self.to_string()
    }
}
impl IntoString for &str {
    fn into(self) -> String {
        self.to_string()
    }
}

impl IntoString for String {
    fn into(self) -> String {
        self
    }
}

#8

That’s right, but String coereces to str, which means that &String coerces to &str which does implement Into<String>. Only in very obscure places this won’t work (but you can circumvent that by using as_str.


#9

Coercion does not work here, because iirc coercion does not apply to generic type parameters, e.g. the simple example would not work. With as_str() it works, but the API is quite weird then.

fn foo<I: Into<String>>(foo: I) -> String {
    let mut a = foo.into();
    a.push('b');
    a
}

fn main() {
    let my_str = "a".to_string();
    println!("{}", foo(&my_str));
}

#10

But in cases where you want to preserve the Vec's functionality, wouldn’t it be appropriate to receive a &Vec instead of a Vec or a &[T]? (Or even an &mut Vec)


#11

@OptimisticPeach


#12

Oh okay, so it’s basically because there’s no point to having an immutable Vec, it’s just a slice at that point, but I should take an &mut Vec if I wanted to add or remove things. That’s a cool insight!