Не удается выйти из заимствованного содержимого при суммировании аргументов командной строки

Это моя первая программа на Rust, и, кажется, я уже сталкивался с ужасной проверкой заимствований. :)

Программа должна прочитать аргументы, переданные в командной строке, суммировать их и вернуть результат. У меня проблемы с разбором аргументов в целые числа.

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());
}

Что не удается с:

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

Как мне поступить?


person Leherenn    schedule 09.04.2015    source источник


Ответы (1)


args — это Vec<String>, а итератор iter возвращает ссылки на строки (&String). Один из способов увидеть типы — попытаться присвоить значение типу единицы измерения ():

let () = args.iter().next();

У которого есть ошибка, которая показывает тип:

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 `()`

В своем закрытии вы пытаетесь автоматически разыменовать (|a, &b|) второе значение. Если бы вы были разыменованы, то String был бы перемещен из вектора, что оставило бы память в векторе в неопределенном состоянии! Если бы мы попытались использовать вектор после этого, мы могли бы вызвать segfault, одну из вещей, которую Rust призван предотвратить.

Проще всего вообще не разыменовывать его (оставив b как &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());
}

Некоторые дополнительные мелкие моменты...

Вам не нужно указывать тип векторных элементов, когда вы collect:

let args: Vec<_> = env::args().collect();

Вам не нужно создавать строку для вывода числа:

println!("{}", sum_args);

И я бы, вероятно, написал это как

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);
}

Предупреждение о чрезмерно умном решении

Если вам нужно суммировать кучу итераторов потенциально ошибочных чисел, вы можете создать тип, который реализует FromIterator и не выделяет никакой памяти:

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 должен даже поддерживать это из коробки:

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);
}
person Shepmaster    schedule 09.04.2015
comment
Разве вектор не должен быть неизменным по умолчанию? Я думал, что заимствование передает право собственности вызывающему абоненту, а затем возвращает его после того, как оно было использовано. Тогда мне нужно перечитать эту часть, спасибо за помощь! - person Leherenn; 09.04.2015
comment
Вектор неизменен, это правда. Даже если бы вектор был изменяемым, вам не было бы позволено перемещать значение таким образом — состояние вектора все равно было бы несогласованным. Вот почему в векторе есть специальные методы для безопасного удаления элементов. И одалживание не передает права собственности, точно так же, как одолжение автомобиля у друзей не дает вам право собственности. ^_^ - person Shepmaster; 09.04.2015