0

In general it's suggested to accept &str instead of String in Rust.

Let's assume i have a couple of functions and a String instance:

use std::collections::HashMap;

fn do_something_str(string: &str) {
    let mut map = HashMap::new();
    map.insert(string.to_owned() /* copying (expensive)? */, "value");
}

fn do_something_string(string: String) {
    let mut map = HashMap::new();
    map.insert(string /* moving (cheap)? */, "value");
}

fn main() {
    let string = String::from("123");
    do_something_str(&string);
    do_something_string(string);
}

Playground

Does copying happen in do_something_str() meaning it will be slower/higher temporary memory consumption?

PS. i know i don't have to call .to_owned() explicitly and the following will also work:

fn do_something_str(string: &str) {
    let mut map = HashMap::new();
    map.insert(string /* copying (expensive)? */, "value");
}

But since a hashmap owns keys i believe it will clone it implicitly. Please correct me, if i'm wrong.

4ntoine
  • 19,816
  • 21
  • 96
  • 220
  • 3
    I'm assuming this post is related to this advice: [Why is it discouraged to accept a reference to a String (&String), Vec (&Vec), or Box (&Box) as a function argument?](https://stackoverflow.com/questions/40006219/why-is-it-discouraged-to-accept-a-reference-to-a-string-string-vec-vec-o) That Q&A is encouraging `&str` over `&String` not necessarily `&str` over `String`. – kmdreko Feb 17 '21 at 11:10
  • @kmdreko Isn't String/&str case different from more generic by-value/by-ref? – 4ntoine Feb 17 '21 at 11:15
  • 1
    No, not really. `&str`, `&[T]`, and `&dyn T` are a bit special because they have different owning types `String`, `Vec`, and `Box` (due to their unsized nature), but the advice for when to use `&T` vs `T` would be the same. – kmdreko Feb 17 '21 at 11:30
  • So what's the right/better answer eventually: `String` or `impl Into`? – 4ntoine Feb 17 '21 at 11:54
  • Up to you, really. There's no real disadvantage to `Into` except that it makes the signature noisier. I wrote a tangentially related answer at [Opposite of Borrow trait for Copy types?](/q/63465672/3650362) which touches on the motivations -- it's really more about caller ergonomics than anything else. – trent Feb 17 '21 at 12:05

2 Answers2

4

In general it's suggested to accept &str instead of String in Rust.

Not quite. The general wisdom is to accept &str instead of &String. That is, when you already intend to operate on a reference, the more general type is considered to be better.

If you need an owned String, it is in fact wasteful to pass a reference only to immediately clone it. Accepting a String in the first place leaves the choice to the caller: if they need to keep a copy of the string for themselves, they can clone(). Else, the caller just moves the String into the HashMap and no cloning is involved.

But since a hashmap owns keys i believe it will clone it implicitly. Please correct me, if i'm wrong.

HashMap does not clone on insertion. That would require a Clone trait bound on HashMap.

HashMap<&str, T> would still "own" the key, but the key in this case is &str, not its owned equivalent (String). Naturally, this would prevent the HashMap from outliving the string that the key references. The following example fails the borrow check:

use std::collections::HashMap;

fn main() {
    let mut map: HashMap<&str, ()> = HashMap::new();
    {
        let key = String::from("foo");
        map.insert(&key, ()); // error[E0597]: `key` does not live long enough
    }
    println!("{:?}", map.get("foo"));
}
justinas
  • 6,287
  • 3
  • 26
  • 36
  • may i wrap-up it as "if you know it will own the instance, just accept it by value"? So it will help the caller move it if it's no needed afterwards or just clone it. – 4ntoine Feb 17 '21 at 11:24
  • @4ntoine sounds about right, and a similar sentiment is expressed in the [linked question](https://stackoverflow.com/questions/61898884/is-it-more-conventional-to-pass-by-value-or-pass-by-reference-when-the-method-ne). – justinas Feb 17 '21 at 11:42
1

Calling .to_owned() on a &str will indeed create an owned copy of the string and is associated with the corresponding performance/memory penaltly.

As you correctly noted, you can also have a HashMap<&str, V>, i.e. a HashMap whose keys are of type &str. This does not require cloning of the string. This however means that the lifetime of the HashMap is limited by the lifetime of the &str.

ConfusedProgrammer
  • 606
  • 1
  • 5
  • 14