It's my first Rust program and it seems I've already encountered the dreaded borrow checker. :)
The program should read the arguments passed in the command line, sum them and return the result. I have troubles parsing the arguments into integers.
use std::env;
fn main() {
let args: Vec<String> = env::args().collect();
let sum_args: i32 =
args
.iter()
.skip(1)
.fold(0, |a, &b| a + b.parse::<i32>().ok().expect("Not an i32!"));
println!("{:?}", sum_args.to_string());
}
Which fails with:
error[E0507]: cannot move out of borrowed content
--> src/main.rs:9:22
|
9 | .fold(0, |a, &b| a + b.parse::<i32>().ok().expect("Not an i32!"));
| ^-
| ||
| |hint: to prevent move, use `ref b` or `ref mut b`
| cannot move out of borrowed content
How should I proceed?
args
is a Vec<String>
, and the iter
iterator returns references to strings (&String
). One trick to see the types is to attempt to assign a value to the unit type ()
:
let () = args.iter().next();
Which has an error that shows the type:
error[E0308]: mismatched types
--> src/main.rs:5:13
|
5 | let () = args.iter().next();
| ^^ expected enum `std::option::Option`, found ()
|
= note: expected type `std::option::Option<&std::string::String>`
= note: found type `()`
In your closure, you are attempting to automatically dereference (|a, &b|
) the second value. If you were able to dereference it, then the String
would be moved out of the vector, which would leave the memory in the vector in an indeterminate state! If we tried to use the vector after this, we could cause a segfault, one of the things Rust is designed to help prevent.
The easiest thing is to not dereference it at all (leaving b
as a &String
):
use std::env;
fn main() {
let args: Vec<String> = env::args().collect();
let sum_args: i32 =
args
.iter()
.skip(1)
.fold(0, |a, b| a + b.parse::<i32>().expect("Not an i32!"));
println!("{:?}", sum_args.to_string());
}
Some additional minor points...
You don't have to specify the vector elements type when you collect
:
let args: Vec<_> = env::args().collect();
You don't need to create a string to print out a number:
println!("{}", sum_args);
And I'd probably have written it as
use std::env;
fn main() {
let args: Vec<String> = env::args().collect();
let sum_args: i32 =
args
.iter()
.skip(1)
.map(|n| n.parse::<i32>().expect("Not an i32!"))
.sum();
println!("{}", sum_args);
}
Overly clever solution warning
If you had to sum up a bunch of iterators of potentially-failed numbers, you could create a type that implements FromIterator
and doesn't allocate any memory:
use std::env;
use std::iter::{FromIterator, Sum};
struct SumCollector<T>(T);
impl<T> FromIterator<T> for SumCollector<T>
where T: Sum
{
fn from_iter<I>(iter: I) -> Self
where I: IntoIterator<Item = T>
{
SumCollector(iter.into_iter().sum())
}
}
fn main() {
let sum: Result<SumCollector<i32>, _> = env::args().skip(1).map(|v| v.parse()).collect();
let sum = sum.expect("Something was not an i32!");
println!("{}", sum.0);
}
Rust 1.16 should even support this out-of-the-box:
use std::env;
fn main() {
let sum: Result<_, _> = env::args().skip(1).map(|v| v.parse::<i32>()).sum();
let sum: i32 = sum.expect("Something was not an i32!");
println!("{}", sum);
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With