Равенство строк в Rust: как работают ссылки и разыменование?

Как новичок в Rust, я работаю над проблемами Project Euler, чтобы помочь мне почувствовать язык. Проблема 4 связана с палиндромами, и я нашел два решения для создания вектора палиндромов, но я не уверен, как они работают.

Я использую вектор строк products, который рассчитывается следующим образом:

let mut products = Vec::new();
for i in 100..500 {
    for j in 500..1000 {
        products.push((i * j).to_string());
    }
}

Чтобы отфильтровать эти продукты только до тех, которые являются палиндромными, у меня есть два следующих решения:

Решение 1.

let palindromes: Vec<_> = products
    .iter()
    .filter(|&x| x == &x.chars().rev().collect::<String>())
    .collect();

Решение 2:

let palindromes: Vec<_> = products
    .iter()
    .filter(|&x| *x == *x.chars().rev().collect::<String>())
    .collect();

Оба они дают правильный результат, но я понятия не имею, почему!

В решении 1 мы сравниваем ссылку на строку со ссылкой на только что созданную строку?

В решении 2 мы разыменуем ссылку на строку и сравниваем ее с разыменованной новой строкой?

На что я рассчитываю:

let palindromes: Vec<_> = products
    .iter()
    .filter(|x| x == x.chars().rev().collect::<String>())
    .collect();

Я надеюсь, что кто-нибудь сможет мне объяснить:

  • В чем разница между двумя моими решениями и почему они оба работают?
  • Почему я не могу просто использовать x без ссылки или разыменования в моей функции фильтрации?

Спасибо!


person piercebot    schedule 02.05.2018    source источник
comment
Оба они дают правильный результат, но я понятия не имею, почему! = ›Теория - это когда вы все знаете, но ничего не работает практика - это когда все работает, но никто не знает, почему в нашей лаборатории совмещены теория и практика, ничего не работает, и никто не знает, почему   -  person Stargateur    schedule 02.05.2018
comment
Я думаю, что это очень похоже на Как перебирать массив? (несмотря на плохой заголовок). Проясняют ли вообще ответы на этот вопрос?   -  person trentcl    schedule 02.05.2018
comment
Обратите внимание, что вам не нужно выделять String, вы можете сравнить итераторы: .filter(|x| x.chars().eq(x.chars().rev())).   -  person Boiethios    schedule 02.05.2018
comment
@piercebot Отлично, Project Euler - это тоже то, с чего я начал ржавчину. Могу я попросить вас придумать проверку палиндрома, которая не требует выделения (collect создает новый String в куче)? :)   -  person kazemakase    schedule 02.05.2018
comment
Ну .. хватит на вызов: p   -  person kazemakase    schedule 02.05.2018
comment
@Boiethios Я не знал про .eq, это супер! Спасибо! Однако применение метода грубой силы было хорошим способом столкнуться с ловушками; спасибо @kazemakase за урок :)   -  person piercebot    schedule 02.05.2018


Ответы (1)


  1. Vec<String>.iter() возвращает итератор по ссылкам (&String).
  2. Аргумент закрытия .filter() принимает ссылку на элемент итератора. Таким образом, тип, который передается в закрытие, - это двойная ссылка &&String.
  3. |&x| сообщает замыканию ожидать ссылки, поэтому x теперь имеет тип &String.

Первое решение: collect возвращает String, из которых & принимает ссылку. x также является ссылкой на строку, поэтому сравнение выполняется между двумя &String.

Второе решение: оператор разыменования * применяется к x, что приводит к String. Правая часть интересна: String результат collect разыменован. В результате получается фрагмент строки, поскольку String реализует Deref<Target=str>. Теперь сравнение выполняется между String и str, что работает, потому что это реализовано в стандартной библиотеке (обратите внимание, что a == b эквивалентно a.eq(&b)).

Третье решение: компилятор объясняет, почему это не работает.

черта std::cmp::PartialEq<std::string::String> не реализована для &&std::string::String

Левая часть - это двойная ссылка на строку (&&String), а правая часть - это просто String. Вам необходимо привести обе стороны к одному и тому же «контрольному уровню». Все эти действия работают:

x.iter().filter(|x| x == &&x.chars().rev().collect::<String>());
x.iter().filter(|x| *x == &x.chars().rev().collect::<String>());
x.iter().filter(|x| **x == x.chars().rev().collect::<String>());
person kazemakase    schedule 02.05.2018
comment
Фантастика! Спасибо за подробное объяснение! Все ли аргументы закрытия ссылаются на ссылки по умолчанию или это зависит от конкретного случая? - person piercebot; 02.05.2018
comment
@piercebot В каждом конкретном случае. Если вы посмотрите определение filter вы можете видеть, что для этого требуется закрытие, которое принимает &Iter::Item. Напротив, например, закрытие, принятое filter_map, принимает Iter::Item, что не является ссылкой. - person kazemakase; 02.05.2018