Using a `let` binding to increase a values lifetime - rust

I wrote the following code to read an array of integers from stdin:
use std::io::{self, BufRead};
fn main() {
let stdin = io::stdin();
for line in stdin.lock().lines() {
let xs: Vec<i32> = line.unwrap()
.trim()
.split(' ')
.map(|s| s.parse().unwrap())
.collect();
println!("{:?}", xs);
}
}
This worked fine, however, I felt the let xs line was a bit long, so I split it into two:
use std::io::{self, BufRead};
fn main() {
let stdin = io::stdin();
for line in stdin.lock().lines() {
let ss = line.unwrap().trim().split(' ');
let xs: Vec<i32> = ss.map(|s| s.parse().unwrap()).collect();
println!("{:?}", xs);
}
}
This didn't work! Rust replied with the following error:
error[E0597]: borrowed value does not live long enough
--> src/main.rs:6:18
|
6 | let ss = line.unwrap().trim().split(' ');
| ^^^^^^^^^^^^^ - temporary value dropped here while still borrowed
| |
| temporary value does not live long enough
...
10 | }
| - temporary value needs to live until here
|
= note: consider using a `let` binding to increase its lifetime
This confuses me. Is it line or ss that doesn't live long enough? And how can I use a let binding to increase their lifetime? I thought I was already using a let?
I've read through the lifetime guide, but I still can't quite figure it out. Can anyone give me a hint?

In your second version, the type of ss is Split<'a, char>. The lifetime parameter in the type tells us that the object contains a reference. In order for the assignment to be valid, the reference must point to an object that exists after that statement. However, unwrap() consumes line; in other words, it moves Ok variant's data out of the Result object. Therefore, the reference doesn't point inside the original line, but rather on a temporary object.
In your first version, you consume the temporary by the end of the long expression, though the call to map. To fix your second version, you need to bind the result of unwrap() to keep the value living long enough:
use std::io::{self, BufRead};
fn main() {
let stdin = io::stdin();
for line in stdin.lock().lines() {
let line = line.unwrap();
let ss = line.trim().split(' ');
let xs: Vec<i32> = ss.map(|s| s.parse().unwrap()).collect();
println!("{:?}", xs);
}
}

It's about the unwrap() call, it's getting the contained object but this reference should outlive the container object, which goes out of scope in the next line (there is no local binding to it).
If you want to get cleaner code, a very common way to write it is:
use std::io::{self, BufRead};
fn main() {
let stdin = io::stdin();
for line in stdin.lock().lines() {
let xs: Vec<i32> = line.unwrap()
.trim()
.split(' ')
.map(|s| s.parse().unwrap())
.collect();
println!("{:?}", xs);
}
}
If not, you can create the binding to the "unwrapped" result and use it.

Related

Read file with BufReader line by line and put in HashMap error borrowed value does not live long enough

I would like to read a file line by line and then process the words. I use HashMap and the entry API for that. However I get a 'borrowed value does not live long enough' error and am puzzled how to fix this.
1 use std::fs::File;
2 use std::io::{BufRead, BufReader};
3 use std::collections::HashMap;
4
5 fn main() {
6
7 let mut wmap: HashMap<_, i32> = HashMap::new();
8 let file = File::open("book1.txt").unwrap();
9 let reader = BufReader::new(file);
10 for (_index, line) in reader.lines().enumerate() {
11 let line = line.unwrap(); // Ignore errors.
12 let words = line.split_whitespace();
13 for word in words {
14 println!("{}.:.{}", _index, word);
15 *wmap.entry(word).or_insert(0) += 1;
16 }
17 }
18
19 }
The error I get is
error[E0597]: `line` does not live long enough
--> example-words.rs:12:17
|
12 | let words = line.split_whitespace();
| ^^^^^^^^^^^^^^^^^^^^^^^ borrowed value does not live long enough
...
15 | *wmap.entry(word).or_insert(0) += 1;
| ---------------- borrow later used here
16 | }
17 | }
| - `line` dropped here while still borrowed
error: aborting due to previous error
For more information about this error, try `rustc --explain E0597`.
I am aware that this is very similar to Borrowed Value Using BufReader and Lines in Extra Function. However I tried to do it all in one main function whereas the other example uses the extra function
read_lines(filename: &str) -> Result<Lines<BufReader<File>>, Error>
Thanks for any help
You are passing a borrowed string slice (&str) to a HashMap that "lives longer" than the borrowed value word. For this to work the borrowed value would need to have the same lifetime as your HashMap OR the HashMap needs to have ownership of the value inside of word. Here's an example:
use std::io;
use std::collections::HashMap;
fn main() {
let mut db = HashMap::new(); //initialize mutable hashmap outside of the loop
loop{
//I start a loop to take in multiple key val arguments from the
//command line but this means each iteration of the loop will
//clean up heap variables and any &str borrowing from these
//variables will be invalid after each iteration and the rust
// borrow checker will let us know if we are trying to access
// these invalid references, hence the compiler error
let mut string = String::new();
io::stdin().read_line(&mut string).unwrap();
let command: Vec<&str> = string.trim().split(" ").collect();
db.insert(command[0], command[1]);
}
}
I end up with the same compiler error:
error[E0597]: `string` does not live long enough
--> main.rs:9:30
|
9 | let command: Vec<&str> = string.trim().split(" ").coll...
| ^^^^^^^^^^^^^ borrowed value does not live long enough
10 | db.insert(command[0], command[1]);
| --------------------------------- borrow later used here
11 | }
| - `string` dropped here while still borrowed
This is because on every iteration of the loop the string slice I intend my HashMap to borrow gets dropped (goes out of scope and is no longer valid) and rust keeps us from having dangling references. Instead change the db.insert(command[0], command[1]) to db.insert(command[0].to_string(), command[2].to_string()). This will convert the &str -> String which will then be "owned" by the HashMap instance and survive for the remainder of the running
program. In your case:
use std::fs::File;
use std::io::{BufRead, BufReader};
use std::collections::HashMap;
fn main() {
let mut wmap: HashMap<_, i32> = HashMap::new();
let file = File::open("book1.txt").unwrap();
let reader = BufReader::new(file);
for (_index, line) in reader.lines().enumerate() {
let line = line.unwrap(); // Ignore errors.
let words = line.split_whitespace();
for word in words {
println!("{}.:.{}", _index, word);
*wmap.entry(word.to_string()).or_insert(0) += 1;
}
}
}
this will compile and run :)
Hope that helps!
As commented by #cdhowie, you need to own the string using word.to_owned().
While it is not an error, Rust naming conventions say that an underscore in front of a variable implies that it is not used, so I renamed _index to index as well.
use std::collections::HashMap;
use std::fs::File;
use std::io::{BufRead, BufReader};
fn main() {
let mut wmap: HashMap<_, i32> = HashMap::new();
let file = File::open("book1.txt").unwrap();
let reader = BufReader::new(file);
for (index, line) in reader.lines().enumerate() {
let line = line.unwrap(); // Ignore errors.
let words = line.split_whitespace();
for word in words {
println!("{}.:.{}", index, word);
*wmap.entry(word.to_owned()).or_insert(0) += 1;
}
}
}

Trimming lines of input for a set in Rust [duplicate]

This question already has answers here:
How to iterate and extract values out of a for loop in Rust
(2 answers)
error[E0597]: borrowed value does not live long enough in While loop
(2 answers)
Return local String as a slice (&str)
(7 answers)
Closed 2 years ago.
This Rust program collects words/lines from the user, and adds each to the variable line_set. I'd like to change the code to trim each word before adding it to line_set.
use std::collections::HashSet;
use std::io;
fn main() {
let mut line_set = HashSet::new();
for i in 1..4 {
let mut line = String::new();
io::stdin()
.read_line(&mut line)
.expect("Failed to read line");
//let line = line.trim();
line_set.insert(line.clone());
if i == 3 {
for l in &line_set {
println!("{}", l);
}
}
}
}
When I try to add a call to String::trim, applied to the current word, the program no longer compiles:
error[E0597]: `line` does not live long enough
--> src/main.rs:12:20
|
12 | let line = line.trim();
| ^^^^ borrowed value does not live long enough
13 | line_set.insert(line.clone());
| -------- borrow later used here
...
19 | }
| - `line` dropped here while still borrowed
I used rustc's --explain switch, and it relates that "This error occurs because a value was dropped while it was still borrowed". I had hoped using the clone method would avoid that problem. How do I get past the error?
str::trim just produces a slice, not another String, so when you call clone on it, you're calling &str's implementation of Clone, which just copies the &str (a cheap pointer copy). Instead, you should use one of the methods to turn the &str into a String, such as to_string, to_owned or more verbosely, String::from.
use std::collections::HashSet;
use std::io;
fn main() {
let mut line_set = HashSet::new();
for i in 1..4 {
let mut line = String::new();
io::stdin()
.read_line(&mut line)
.expect("Failed to read line");
line_set.insert(line.trim().to_owned());
if i == 3 {
for l in &line_set {
println!("{}", l);
}
}
}
}
(playground)

Why does a variable holding the result of Vec::get_mut not need to be mutable?

I have the following code:
fn main() {
let mut vec = Vec::new();
vec.push(String::from("Foo"));
let mut row = vec.get_mut(0).unwrap();
row.push('!');
println!("{}", vec[0])
}
It prints out "Foo!", but the compiler tells me:
warning: variable does not need to be mutable
--> src/main.rs:4:9
|
4 | let mut row = vec.get_mut(0).unwrap();
| ----^^^
| |
| help: remove this `mut`
Surprisingly, removing the mut works. This raises a few questions:
Why does this work?
Why doesn't this work when I use vec.get instead of vec.get_mut, regardless of whether I use let or let mut?
Why doesn't vec work in the same way, i.e. when I use let vec = Vec::new(), why can't I call vec.push()?
vec.get_mut(0) returns an Option<&mut String>, so when you unwrap that value you will have a mutable borrow of a String. Remember, that a let statement's left side is using pattern matching, so when your pattern is just a variable name you essentially say match whatever is on the right and call it name. Thus row matches against &mut String so it already is mutable.
Here's a much simpler and more straightforward example to illustrate the case (which you can try in the playground):
fn main() {
let mut x = 55i32;
dbg!(&x);
let y = &mut x; // <-- y's type is `&mut i32`
*y = 12;
dbg!(&x);
}

How to push a value to a Vec and append it to a String at the same time?

I want to write a program that sets the shell for the system's nslookup command line program:
fn main() {
let mut v: Vec<String> = Vec::new();
let mut newstr = String::from("nslookup");
for arg in std::env::args() {
v.push(arg);
newstr.push_str(&format!(" {}", arg));
}
println!("{:?}", v);
println!("{}", newstr);
}
error[E0382]: borrow of moved value: `arg`
--> src/main.rs:6:41
|
5 | v.push(arg);
| --- value moved here
6 | newstr.push_str(&format!(" {}", arg));
| ^^^ value borrowed here after move
|
= note: move occurs because `arg` has type `std::string::String`, which does not implement the `Copy` trait
How to correct the code without traversing env::args() again?
Reverse the order of the lines that use arg:
for arg in std::env::args() {
//newstr.push_str(&format!(" {}", arg));
write!(&mut newstr, " {}", arg);
v.push(arg);
}
Vec::push takes its argument by value, which moves ownership of arg so it can't be used anymore after v.push(arg). format! and related macros implicitly borrow their arguments, so you can use arg again after using it in one of those.
If you really needed to move the same String to two different locations, you would need to add .clone(), which copies the string. But that's not necessary in this case.
Also note that format! creates a new String, which is wasteful when all you want is to add on to the end of an existing String. If you add use std::fmt::Write; to the top of your file, you can use write! instead (as shown above), which is more concise and may be more performant.
See also
What are move semantics in Rust?
error: use of moved value - should I use "&" or "mut" or something else?
Does println! borrow or own the variable?
You can do like that:
fn main() {
let args: Vec<_> = std::env::args().collect();
let s = args.join(" ");
println!("{}", s);
}
First, you create the vector, and then you create your string.

How do I parse a vector into a function?

The idea is to send a set of characters of a vector and let the function display the current correct guesses.
Here is my main:
fn main() {
let mut guessedLetters = vec![];
displayWord(guessedLetters);
}
And here is the function:
fn displayWord(correctGuess: Vec<char>) {
let mut currentWord = String::new();
for x in 0..5 {
currentWord.push(correctGuess[x]);
}
println!("Current guesses: {}", currentWord);
}
I don't know what I'm supposed to write inside the parameters of displayWord.
There's a couple of things wrong with your code.
The first error is pretty straight forward:
--> src/main.rs:38:25
|
38 | displayWord(guessed_Letters);
| ^^^^^^^^^^^^^^^ expected char, found enum `std::option::Option`
|
= note: expected type `std::vec::Vec<char>`
found type `std::vec::Vec<std::option::Option<char>>`
The function you wrote is expecting a vector a characters ... but you're passing it a vector of Option<char>. This is happening here:
guessed_Letters.push(line.chars().nth(0));
According to the documentation, the nth method returns an Option. The quick fix here is to unwrap the Option to get the underlying value:
guessed_Letters.push(line.chars().nth(0).unwrap());
Your next error is:
error[E0382]: use of moved value: `guessed_Letters`
--> src/main.rs:38:25
|
38 | displayWord(guessed_Letters);
| ^^^^^^^^^^^^^^^ value moved here in previous iteration of loop
|
= note: move occurs because `guessed_Letters` has type `std::vec::Vec<char>`, which does not implement the `Copy` trait
This is transferring ownership of the vector on the first iteration of the loop and the compiler is telling you that subsequent iterations would be in violation of Rust's ownership rules.
The solution here is to pass the vector by reference instead:
displayWord(&guessed_Letters);
..and your method should also accept a reference:
fn displayWord(correctGuess: &Vec<char>) {
let mut currentWord = String::new();
for x in 0..5 {
currentWord.push(correctGuess[x]);
}
println!("Current guesses: {}", currentWord);
}
This can be shortened to use a slice and still work:
fn displayWord(correctGuess: &[char]) {

Resources