How to pass slice of struct's String field?


#1

Hi. I have I want to insert something into hash table using slice - &str, however,
some strange borrow-checker related error occurs, like this:

use std::collections::HashMap;
use std::fmt;

#[derive(Debug)]
pub struct TestStruct {
from: String,
to: String,
}

fn main() {
let mut table: HashMap<&str, TestStruct> = HashMap::with_capacity(251);
let a1 = TestStruct { from: String::from(“a”), to: String::from(“b”) };
let a2 = TestStruct { from: String::from(“c”), to: String::from(“d”) };
let cln = a2.from.clone();
table.insert(&cln, a1);
}

I am making clone of struct’s String field, then I use & to make slice out of String.
Howevre, I got:
error[E0597]: cln does not live long enough
–> src/main.rs:16:1
|
15 | table.insert(&cln, a1);
| — borrow occurs here
16 | }
| ^ cln dropped here while still borrowed
|
= note: values in a scope are dropped in the opposite order they are created

table.insert(cln.as_str(), a1); // doesn’t work either
That seems strange, because cln is slice out of a2’s “from” field, not slice out of a1’s “from” field.
How could I resolve it? Thanks.


#2

In Rust values are destroyed in reverse order of their creation, so your main works as if it was:

let mut table: HashMap<&str, TestStruct> = HashMap::with_capacity(251);
let a1 = TestStruct { from: String::from("a"), to: String::from("b") };
let a2 = TestStruct { from: String::from("c"), to: String::from("d") };
let cln = a2.from.clone();
table.insert(&cln, a1);

drop(cln);
drop(a2);
// drop(a1); // was moved
drop(table);

So cln is destroyed before the table. Changing the order makes it compile:

let cln = a2.from.clone();
let mut table: HashMap<&str, TestStruct> = HashMap::with_capacity(251);
table.insert(&cln, a1);

But it’s not very useful. In general, references in Rust are only for temporary things. In practice you’d probably want the hashmap to outlive the function it was created in, so you’d need to use permanently-living types such as String.

So change HashMap<&str> to HashMap<String>.


#3

Works, thanks. Just a few more related questions:

  1. What’s use case of HashMap<&str,…>?
    (I found that signature in docs’ first example https://doc.rust-lang.org/std/collections/struct.HashMap.html#examples)

  2. Every time I want to pass key to one of HashMap’s methods I should copy String by clone and pass it as a key?

  3. Which of two cases that you show is idiomatic, preferable?


#4

The docs use &str, because string literals have type &'static str. It’s not useful in general.

Only clone() (or call to_owned()) when Rust complains “cannot move value …”. A single copy of a String can be passed around without copying.

In structs and return types you generally use owned values. Inside functions and in functions arguments you generally use references.


#5

Au contraire.

HashMap<&str, ...> can (and should) be used when the strings the key is derived of are already owned by another structure, for example a vector. As long as the vector lives at least as long as the hashmap, and as long as you don’t mutate the vector during that time (both are statically guaranteed by Rust).

This is especially useful if the hashmap is not the only structure to use the &str. There might be multiple maps, sets, slices, etc. This is also the basis of a pattern called an “arena”, where you put (“allocate”) owned data items like strings into a structure that’s basically a vector (modulo some tricks to allow you to allocate more items even while others are borrowed). All data is then freed once the arena is destroyed.


#6

Given that Rust doesn’t support self-referential structs I still wouldn’t call that a general usage. These are optimizations and special cases for making temporary views of borrowed data.

Also keep in mind context of this answer — if someone is fighting with the borrow checker it’s better to give advice that maximizes productivity rather than one that maximizes efficiency.

I don’t think anything can ever get semantically wrong if you use owned String instead of &str, so it’s definitely better to err on that side when borrow checker complains. It only creates some overhead, but a program that compiles is always faster than a program that doesn’t compile :slight_smile:


#7

You don’t need self-referential structs to make use of containers using borrowed data. Also, “not useful in general” sounds to me a lot like “don’t bother thinking about it”, which is not helpful if the goal is to understand Rust and its types.


#8

There’s a middle ground - Rc<str>. This is useful when you want/need owned data (eg API requires 'static) but don’t want to deeply clone Strings.