Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

"Cannot move out of borrowed content" while summing command line arguments

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?

like image 537
Leherenn Avatar asked Apr 09 '15 13:04

Leherenn


1 Answers

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);
}
like image 185
Shepmaster Avatar answered Oct 18 '22 04:10

Shepmaster