I'm very new to Rust (coming over from Python) and I've been trying to go through the book. I'm currently at the end of chapter 8, trying to solve the suggested problems.
I'm looking at the employee interface one, which, is the following:
Using a hash map and vectors, create a text interface to allow a user to add employee names to a department in a company. For example, “Add Sally to Engineering” or “Add Amir to Sales.” Then let the user retrieve a list of all people in a department or all people in the company by department, sorted alphabetically.
As a start, I just wanted to implement the part with adding people to a particular department and I can't quite get it to work, even though the code is quite simple. I have the following code:
fn main() {
let mut company: HashMap<&str, Vec<&str>> = HashMap::new();
loop {
let mut input = String::new();
// let mut company = company.clone();
println!("Enter query below:");
io::stdin()
.read_line(&mut input)
.expect("Failed to read line!");
let input: &str = &input[..];
let words: Vec<&str> = input.trim().split_whitespace().collect();
assert!(words.len() >= 4, "ERROR: Query must be at least 4 words long!");
let employee: &str = &words[1];
let department: &str = &words[3];
let department_employees: &mut Vec<&str> = company.entry(&department).or_insert(Vec::new());
department_employees.push(&employee);
println!("Company has been updated:\n{company:?}");
}
}
As it is, it raises the following error:
error[E0597]: `input` does not live long enough
--> src/main.rs:34:28
|
28 | let mut input = String::new();
| --------- binding `input` declared here
...
34 | let input: &str = &input[..];
| ^^^^^ borrowed value does not live long enough
...
40 | let department_employees: &mut Vec<&str> = company.entry(&department).or_insert(Vec::new());
| ------- borrow later used here
...
43 | }
| - `input` dropped here while still borrowed
For more information about this error, try `rustc --explain E0597`.
error: could not compile `employee_administration` (bin "employee_administration") due to 1 previous error
From reading the above and also doing rustc --explain E0597, my understanding (which may or may not be correct) is that the issue is that the hashmap is still holding a reference to the input data after the input data has gone out of scope (which I guess happens after the loop has gone through an iteration?).
Through trial and error, I managed to figure out that these errors no longer show up if I add the line that is currently commented out. But there are still a few issues 1) I don't understand why this makes the errors stop, 2) it seems pretty hacky and 3) it doesn't even work; the hashmap is emptied at the start of each iteration, meaning I never get a hashmap with more than one person or department.
So I'm left a bit confused and stumped at this point and was hoping to get a few pointers on what the issue is / what I'm failing to understand / what I can do to fix it. Thank you for your time.
Short answer: In order to store strings in company beyond a single iteration, you need to convert the &strs to Strings and store those instead.
So, change the type of company to:
let mut company: HashMap<String, Vec<String>> = HashMap::new();
and change these lines:
let employee: &str = &words[1];
let department: &str = &words[3];
let department_employees: &mut Vec<&str> = company.entry(&department).or_insert(Vec::new());
department_employees.push(&employee);
to:
let employee: String = words[1].to_string();
let department: String = words[3].to_string();
let department_employees: &mut Vec<String> = company.entry(department).or_insert(Vec::new());
department_employees.push(employee);
A couple of side notes:
The line let input: &str = &input[..]; is pretty much pointless. Anything you can do with a &str can be done on a String as well due to deref coercion.
Unless you added the annotations just so that you, the human programmer, could read & understand the code better, you often don't need to explicitly declare the types of variables, as Rust infers them automatically. Return values from the collect() and parse() methods are some of the few exceptions, as the multiple possible return types mean you need to either annotate the variable you're storing the results in or else use a turbofish.
Thank you very much! I guess I've had the mentality of trying to use &str as much as possible, since it's more general, but it seems like I'm overdoing it a bit :).
(I know it says not to respond after marking it as solved, but it felt too weird to just mark it as solved without at least saying thank you).
I just want to add an explanation of what happened when you added let mut company = company.clone();. A key thing to keep in mind is that a HashMap doesn't have reference semantics, unlike what I understand a Python dictionary has. This means that when you clone it, you create a disjoint copy of it, including a disjoint copy of the content. This copy is also within the loop scope, so it's fine to other loop-scoped references inside it.
"As much as possible" clearly excludes the cases where it's impossible.
No, they are not. Borrowing and owning types serve different purposes. Neither is strictly more general.
References are temporary views into data owned by someone else. You can't use a reference to keep the referent alive; you have to ensure (by appropriate scoping) that the referent does live long enough.
The compiler is not a garbage collector. It checks whether you are using your references correctly. If you don't, you get a compile-time error, there won't be any magical dynamic scope extension.
If you want reference counting, use Rc or Arc. But they are rarely needed; most problems have a simple and clear ownership structure. If you want to preserve data in collections, you'll have to put owned types in them.
Thank you for your response. To clarify, when I say "more general", I mean compared to passing a reference to a string like &String, since &str also works with string literals. This (pretty nice) property makes it so that I am perhaps a bit quick to just throw a &str in there. In this case, the solution was of course to take ownership, but I hadn't really figured that out yet!
In general, I'd say I need to think a bit more about when to take ownership vs when not to do it. I think there's still part of me that's a bit scared of doing that, because I'm like "I don't want to destroy my data" lol. So I think I have a bit of a tendency to go, "oh I have a string, I'll just pass in &str", without thinking enough about ownership.
So yeah, lots of stuff to learn and to get used to
Ah okay, thanks, that makes sense. In my head, I was imagining that on iterations after the first, the copy would be a copy of the one from the previous iteration. But yeah, if it gets destroyed after each iteration, then it makes perfect sense why this was happening. I guess this whole thing of starting the next iteration exactly where you left off in the previous one is another Python thing I was subconsciously taking for granted without thinking enough about it
One very surprising property of Rust is that duality: experience brought from other languages (especially common GC-based languages like Java or Python) help surprisingly little, but intuition that is needed to grok Rust… you already have it, just look on real world entities!
String would become a book, potentially very thick and heavy book. And then &String would become a library card for thank book which allows you to read it. And &str is permit to look on text in something (could be book or a whiteboard… anything with text, essentially). And then &mut String is a permit to write something in that book (so more like a workbook than a textbook).
And if you'll apply these analogues then many rules would become much cleaner: I'm not sure anyone still does that, but my parents were telling about times when schools had not enough textbooks so they gave one per two pupils and they were shared (just quite literally: put the textbook on table between two pupil places and they both can read it) and with OHP you may even share text with the whole class!
But workbooks would still be made and bought for each pupil, trying to share them would just create a huge mess. Although they could be shared in “read-only mode” when someone discusses solution but doesn't change them.
Rust's terms “ownership and borrow system” alludes strongly to these ideas and if you recall how many things humanity invented to manage ownership of things then the majority of corner cases are covered. Including shared ownership (think corporations with shares), weak references (phone book entry) and so on.
Not everything, of course: every analogy breaks when you stretch it too far (difference between Arc and Rc is too subtle to have “real world entities” analogy, e.g.), but “ownership and borrow system” as it's practised by laymans describes Rust behavior surprisingly well… and much better than attempts to bring intuition from many other languages.
Right, I forgot to mention that writing let company = ... doesn't assign to the other company variable. It creates a new variable with the same name and the other one stays alive "in the background" to the end of its scope. The new variable is shadowing the old one within the loop scope.
Thank you for the really nice and clear explanation! It's great that you can make these simple analogies and have them actually work for the most part, since my background is in a field where this is almost never the case!
I'll try to keep this in mind as I write my code. Sometimes even if you feel like how have an okay understanding of a concept, when you're actually writing code, it can be pretty easy to fall into the habits of the language you're used to programming in. In that sense, prior experience can be a bit of a curse. I guess this is something I'll have to stay vigilant about!
Good news: treat ownership in Rust like you would treat ownership in real world with real, physical, objects is surprisingly robust and versatile analogy.
Bad news: this doesn't mean that ownership in Rust is “easy”. Yes, it maps to ownership handling in our society (outside of any computers) extremely well, but that also means that discussions about who owns what and when map to real-world court cases about ownership… and if you'll recall that these can easily go on for decades…
Advice is the same as in real world, though: don't overcomplicate your ownership story unless you have to and there would be no need for lawyering (normal one in an analogy, language lawyering in case of Rust).
Notice you wrote “passing”. &str is indeed better for passing to a function (often, not always). But when you have your HashMap outside the loop, that's not passing, that's storing the value in your application's data structures, and this works somewhat oppositely: storing a String (owned) is more flexible than storing a &str (borrowed).
(And if you look deeper, all principles like this have two sides: when you gain one property you lose another. A function should take String when it's going to store that String in a data structure, to avoid extra copying. A data structure might prefer &str when the data structure is itself more temporary than the string.)
Don't try to think in special cases! This is not about "loops starting the next iteration where you left off". This is not a "python" thing, either. It doesn't even have anything to do with the loop.
This is purely because of scoping, that works the same way everywhere in Rust.
The variable in the inner scope is not the same as the variable in the outer scope. They have lexically the same name, but that means nothing (except that you can't directly access the outer one while the inner one is in scope). A let declaration always introduces a new variable. It is an initialization, not an assignment.
The loop merely executes the same scope repeatedly, but this is independent of the behavior of the loop itself. You'd find exactly the same behavior if you replaced the loop with a plain block.
Thank you for clarifying. I think on the whole, the way scopes work in Rust is probably a lot more intuitive and sensible. I'm just used to Python, which allows you to do some pretty weird things. Like defining a variable outside of a function and then just using the variable in the function (without passing it to the function). Or even wackier stuff. Like if you pass in foo to the constructor of a class and do self.foo = foo, then in a totally different method inside the same class, you can actually still use the foo variable (without the self.), even though that would basically never make any sense to do (this specific example in some code a colleague had written was once the source of a bug that took me hours to find ). And functions are probably the most isolated thing you'll find in Python! As a consequence, I am perhaps not that used to thinking about the scope of variables as often as I should. I'll have to be more vigilant about not falling into these habits.