Правила заимствования Rust в цикле `for ... in` - PullRequest
2 голосов
/ 29 февраля 2020

Почему все эти три print_max функции работают? Какой из них лучший? Является ли for number in number_list ярлыком для for number in number_list.iter()?

fn main() {
    let number_list = vec![34, 50, 25, 100, 65];

    print_max_1(&number_list);
    print_max_2(&number_list);
    print_max_3(&number_list);
}

fn print_max_1(number_list: &[u16]) {
    let mut largest = &number_list[0]; // borrow the first number
    for number in number_list.iter() { // borrowing?
        if number > largest {
            largest = number;
        }
    }
    println!("The largest number is {}", largest);
}

fn print_max_2(number_list: &[u16]) {
    let mut largest = &number_list[0]; // borrow the first number
    for number in number_list {        // shortcut for .iter()?
        if number > largest {
            largest = number;
        }
    }
    println!("The largest number is {}", largest);
} 

fn print_max_3(number_list: &[u16]) {
    let mut largest = number_list[0];    // copying?
    for &number in number_list.iter() {  // borrowing?
        if number > largest {            // no error
            largest = number;
        }
    }
    println!("The largest number is {}", largest);
}

Почему это не сработает?

fn print_max_4(number_list: &[u16]) {
    let mut largest = &number_list[0];
    for &number in number_list {
        if number > largest {
            largest = number;
        }
    }
    println!("The largest number is {}", largest);
} 

В сообщении об ошибке говорится, что largest означает &u16, в то время как number - это u16. Почему не number &u16?

Ответы [ 2 ]

3 голосов
/ 29 февраля 2020

Давайте разберемся с ними один за другим.

print_max_1

Здесь largest - это изменяемая переменная, которая содержит неизменную ссылку на u16 (т.е. она содержит &u16). В пределах l oop, number также является &u16, который, как и largest, заимствован из number_list. И number, и larger неявно разыменовываются для выполнения сравнения. Если значение, на которое ссылается number, превышает значение, на которое ссылается larger, вы сохраняете копию неизменяемой ссылки, содержащейся в number, в largest.

print_max_2

В этом случае, поскольку number_list само заимствовано, анализ print_max_2 идентичен print_max_1.

print_max_3

Здесь largest представляет собой u16. Вы правы, что number_list[0] скопировано, но стоит отметить, что эта копия дешевая . В пределах l oop каждый элемент number_list копируется и сохраняется непосредственно в number. Если number больше largest, вы сохраните новое наибольшее значение непосредственно в largest. Это наиболее оптимальная из трех написанных вами реализаций, поскольку вы избавляетесь от всей ненужной косвенности, которую представляют ссылки (т. Е. Указатели).

print_max_4

Еще раз Вы сохраняете ссылку на первый элемент number_list в largest, т.е. largest - это &u16. Аналогично, как и в случае print_max_3, number - это u16, в котором будут храниться копии элементов из number_list. Однако, как вы заметили, эта функция является дочерним объектом проблемы.

Внутри l oop компилятор укажет на две ошибки:

  1. Вы пытаетесь сравнить два разных типа которые не имеют PartialOrder, а именно largest, который является &u16 и number, который является u16. Русту не нужно пытаться угадать, что вы имеете в виду под этим сравнением, поэтому, чтобы исправить это, вы должны сделать оба типа number и largest одного типа. В этом случае вам нужно явно разыменовать largest с помощью оператора *, который позволит вам сравнить u16, на который он указывает, с u16, содержащимся в number. Это окончательное сравнение выглядит как
    if number > *largest { ... }
    
  2. Вы пытаетесь сохранить u16 в переменной типа &u16, что не имеет смысла. К сожалению, здесь вы наткнетесь на стену. В пределах l oop все, что у вас есть, - это значение числа, которое вы скопировали с number_list, но largest должно содержать ссылку до u16. Мы не можем просто заимствовать number здесь (например, записывая largest = &number), так как number будет отброшено (то есть go вне области видимости) в конце l oop. Единственный способ разрешения - это возврат к print_max_2 путем сохранения самого максимального значения вместо указателя на него.

Относительно того, является ли for number in number_list ярлык для for number in number_list.iter(), ответ твердый нет . Первый из них станет владельцем number_list, и на каждой итерации number станет владельцем следующего значения в number_list. В отличие от этого, последний выполняет только заимствование, и во время каждой итерации l oop, number получает неизменную ссылку на следующий элемент number_list.

В этом конкретном случае c эти две операции кажутся идентичными, поскольку получение права владения неизменной ссылкой просто влечет за собой создание копии, что оставляет первоначального владельца нетронутым. Для получения дополнительной информации см. этот ответ на связанный вопрос о разнице между .into_iter() и .iter().

1 голос
/ 29 февраля 2020

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

Ваша переменная 'number_list' это std::vec::Vec. Затем вы используете срез для сигнатур аргументов функции. В Vector есть реализация черты Deref. В ржавчине это конкретное расположение использует Deref coercion для преобразования вектора с упомянутой чертой Дерефа в std::slice.

Однако, как векторы, так и срезы могут быть повторяется с использованием для l oop. Все, что реализует черту std::iter::Iterator. Вектор не делает этого, а реализует std::iter::IntoIterator, что, как он выражается, By implementing IntoIterator for a type, you define how it will be converted to an iterator. This is common for types which describe a collection of some kind. Взгляните на Реализация итератора для получения более подробной информации. В частности, строки:

Все Итераторы реализуют IntoIterator, просто возвращая себя. Это означает две вещи:

Если вы пишете итератор, вы можете использовать его с a для l oop. Если вы создаете коллекцию, реализация IntoIterator для нее позволит использовать вашу коллекцию с параметром for l oop.

. Вы обнаружите, что Rust предоставляет множество черт конверсии, а некоторые автомат c скрытое поведение. Благодаря широкому использованию типов в Rust это помогает облегчить некоторые необходимые преобразования. См. From и Into для получения более подробной информации.

Я ненавидел это в C ++ (скрытый код), однако в Rust все не так плохо. Если компилятор позволяет вам это сделать, то вы, вероятно, нашли то, что вам нужно. Иногда автоматический c способ может быть неадекватным, и вам может потребоваться использовать поддерживающие методы / функции, чтобы туда добраться.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...