I'm working with CSV so I need to trim newlines and split on commas for each line, and filter out any lines that have a '?' in them.
let instances: Vec<Vec<&str>> = file.lines()
.map(|x| x.unwrap())
.filter(|x| !(x.contains("?")))
.map(|x| x.as_slice().trim_chars('\n').split_str(",").collect()).collect();
This is the compiler error message I'm getting:
.../src/main.rs:13:18: 13:19 error: `x` does not live long enough
.../src/main.rs:13 .map(|x| x.as_slice().trim_chars('\n').split_str(",").collect()).collect();
^
.../src/main.rs:7:11: 21:2 note: reference must be valid for the block at 7:10...
.../src/main.rs:7 fn main() {
.../src/main.rs:8 let path = Path::new("./...");
.../src/main.rs:9 let mut file = BufferedReader::new(File::open(&path));
.../src/main.rs:10 let instances: Vec<Vec<&str>> = file.lines()
.../src/main.rs:11 .map(|x| x.unwrap())
.../src/main.rs:12 .filter(|x| !(x.contains("?")))
...
.../src/main.rs:13:18: 13:72 note: ...but borrowed value is only valid for the block at 13:17
.../src/main.rs:13 .map(|x| x.as_slice().trim_chars('\n').split_str(",").collect()).collect();
I don't understand how the lifetimes of the string types in Rust are supposed to be used in this context. Changing instances to Vec<Vec<String>> doesn't fix the problem either.
What's extra confusing to me is that following works with a single String:
let x: Vec<&str> = some_string.as_slice().trim_chars('\n').split_str(",").collect();
What am I doing wrong with the lifetime of these values to cause this compiler error?
If iterator adapters are not an idiomatic approach to this problem please explain why and how I should approach this differently.
The x &str is a reference to the contents of a String, the ones yielded by lines(). A &str can only live as long as the String it is a reference into, and you’re not storing the String anywhere. You would need to either store the lines in another variable:
let lines = file.lines().map(|x| x.unwrap()).collect::<Vec<_>>();
let instances: Vec<Vec<&str>> = lines.iter()
.filter(|x| !(x.contains("?")))
.map(|x| x.trim_chars('\n').split_str(",").collect()).collect();
Or else you would convert all of the &strs into Strings:
let instances: Vec<Vec<String>> = file.lines()
.map(|x| x.unwrap())
.filter(|x| !(x.contains("?")))
.map(|x| x.trim_chars('\n').split_str(",")
.map(|x| x.into_string()).collect()).collect();
As an incidental note, the collect() calls can be written collect::<Vec<_>>(), allowing you to remove the type annotation from the instances variable. Which is better? Up to you to decide.
Related
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);
}
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.
I'm trying to chunk an vector of uneven length strings into a vector of even length strings. The laziest way I could think of doing this is to join the arguments into a string, convert the chars to a vector, and then use Vec::chunks. Unfortunately, I'm running into issues trying to collect the chunks into strings.
let args: Vec<String> = ["123", "4", "56"].iter().map(|&s| s.into()).collect();
let result: Vec<String> = args
.join(" ")
.chars()
.collect::<Vec<_>>()
.chunks(2)
.map(|c| c.collect::<String>())
.collect::<Vec<String>>();
assert_eq!(["12", "34", "56"], result);
Results in the error:
error[E0599]: no method named `collect` found for type `&[char]` in the current scope
--> src/main.rs:9:20
|
9 | .map(|c| c.collect::<String>())
| ^^^^^^^
|
= note: the method `collect` exists but the following trait bounds were not satisfied:
`&mut &[char] : std::iter::Iterator`
`&mut [char] : std::iter::Iterator`
You weren't far off:
let result: Vec<String> = args
.join("")
.chars()
.collect::<Vec<_>>()
.chunks(2)
.map(|x| x.iter().cloned().collect())
.collect();
println!("{:?}", result);
You probably don't want a space when joining them together.
You need to convert each chunk (which is a &[char]) into an iterator via .iter(). You then have to convert the iterated type from a &char to a char via .cloned().
I might write this using Itertools::chunks though:
use itertools::Itertools; // 0.8.0
fn main() {
let args = ["123", "4", "56"];
let together = args.iter().flat_map(|x| x.chars());
let result: Vec<String> = together
.chunks(2)
.into_iter()
.map(|x| x.collect())
.collect();
println!("{:?}", result);
}
flat_map avoids the need to create a String, it just chains one iterator to the next.
Itertools::chunks allows the programmer to not create an intermediate Vec. Instead, it has an internal vector that, IIRC, will only store up to n values in it before yielding a value. This way you are buffering a smaller amount of items.
This program accepts an integer N, followed by N lines containing two strings separated by a space. I want to put those lines into a HashMap using the first string as the key and the second string as the value:
use std::collections::HashMap;
use std::io;
fn main() {
let mut input = String::new();
io::stdin().read_line(&mut input)
.expect("unable to read line");
let desc_num: u32 = match input.trim().parse() {
Ok(num) => num,
Err(_) => panic!("unable to parse")
};
let mut map = HashMap::<&str, &str>::new();
for _ in 0..desc_num {
input.clear();
io::stdin().read_line(&mut input)
.expect("unable to read line");
let data = input.split_whitespace().collect::<Vec<&str>>();
println!("{:?}", data);
// map.insert(data[0], data[1]);
}
}
The program works as intended:
3
a 1
["a", "1"]
b 2
["b", "2"]
c 3
["c", "3"]
When I try to put those parsed strings into a HashMap and uncomment map.insert(data[0], data[1]);, the compilation fails with this error:
error: cannot borrow `input` as mutable because it is also borrowed as immutable [E0502]
input.clear();
^~~~~
note: previous borrow of `input` occurs here; the immutable borrow prevents subsequent moves or mutable borrows of `input` until the borrow ends
let data = input.split_whitespace().collect::<Vec<&str>>();
^~~~~
note: previous borrow ends here
fn main() {
...
}
^
I don't understand why this error would come up, since I think the map.insert() expression doesn't borrow the string input at all.
split_whitespace() doesn't give you two new Strings containing (copies of) the non-whitespace parts of the input. Instead you get two references into the memory managed by input, of type &str. So when you then try to clear input and read the next line of input into it, you try overwriting memory that's still being used by the hash map.
Why does split_whitespace (and many other string methods, I should add) complicate matters by returning &str? Because it's often enough, and in those cases it avoid unnecessary copies. In this specific case however, it's probably best to explicitly copy the relevant parts of the string:
map.insert(data[0].clone(), data[1].clone());
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.