Почему использование by_ref (). Take () отличается между чертами Iterator и Read? - PullRequest
0 голосов
/ 26 сентября 2018

Вот две функции:

fn foo<I>(iter: &mut I)
where
    I: std::iter::Iterator<Item = u8>,
{
    let x = iter.by_ref();
    let y = x.take(2);
}

fn bar<I>(iter: &mut I)
where
    I: std::io::Read,
{
    let x = iter.by_ref();
    let y = x.take(2);
}

Пока первый компилируется нормально, второй выдает ошибку компиляции:

error[E0507]: cannot move out of borrowed content
  --> src/lib.rs:14:13
   |
14 |     let y = x.take(2);
   |             ^ cannot move out of borrowed content

Сигнатуры by_ref и takeпочти идентичны по признакам std::iter::Iterator и std::io::Read, поэтому я предположил, что если первое скомпилируется, второе скомпилируется тоже.Где я ошибаюсь?

Ответы [ 2 ]

0 голосов
/ 27 сентября 2018

Это действительно сбивающее с толку сообщение об ошибке, и причина, по которой вы его получаете, довольно тонкая.Ответ от ozkriff правильно объясняет, что это потому, что черта Read не находится в области видимости.Я хотел бы добавить немного больше контекста и объяснение, почему вы получаете конкретную ошибку, которую вы видите, а не ошибку, что метод не найден.

Метод take() в Read и Iterator принимает self по значению, или, другими словами, он потребляет свой получатель.Это означает, что вы можете позвонить, только если у вас есть право собственности на получателя.Функции в вашем вопросе принимают iter по изменяемой ссылке, поэтому они не владеют базовым I объектом, поэтому вы не можете вызвать <Iterator>::take() или <Read>::take() для базового объекта.

Однако, как указал ozkriff, стандартная библиотека предоставляет реализации «пересылки» Iterator и Read для изменяемых ссылок на типы, которые реализуют соответствующие признаки.Когда вы вызываете iter.take(2) в своей первой функции, вы фактически заканчиваете тем, что вызываете <&mut Iterator<Item = T>>::take(iter, 2), который использует только вашу изменяемую ссылку на итератор, а не сам итератор.Это совершенно верно;в то время как функция не может использовать сам итератор, поскольку ей не принадлежит эта ссылка, функция владеет ссылкой.Однако во второй функции вы в итоге вызываете <Read>::take(*iter, 2), который пытается использовать базовый читатель.Поскольку у вас нет этого читателя, вы получаете сообщение об ошибке, объясняющее, что вы не можете переместить его из заимствованного контекста.

Так почему второй вызов метода разрешает другой метод?Ответ Озкриффа уже объясняет, что это происходит потому, что черта Iterator входит в стандартную прелюдию, а черта Read по умолчанию не входит в сферу.Давайте посмотрим на метод поиска более подробно.Это описано в разделе «Выражения вызова метода» справочника по языку Rust:

Первым шагом является создание списка возможных типов получателей.Получите их, многократно разыменовывая тип получающего выражения, добавляя каждый встреченный тип в список, затем, в конце, пытайтесь выполнить принудительное приведение в конце и добавьте тип результата, если это успешно.Затем для каждого кандидата T добавьте &T и &mut T в список сразу после T.

Согласно этому правилу наш список типов кандидатов будет

&mut I, &&mut I, &mut &mut I, I, &I, &mut I

Затем для каждого типа кандидата T найдите видимый метод с приемником этого типа в следующих местах:

  1. T 's встроенные методы (методы, реализованные непосредственно в T).

  2. Любой из методов, предоставляемых видимой чертой, реализованной T.Если T является параметром типа, сначала ищутся методы, предоставляемые границами признаков для T.Затем ищутся все остальные методы в области видимости.

Для случая I: Iterator этот процесс начинается с поиска метода take() в &mut I.Для &mut I нет внутренних методов, поскольку I является универсальным типом, поэтому мы можем пропустить шаг 1. На шаге 2 мы сначала ищем методы на границах признаков для &mut I, но есть только границы признаков дляI, поэтому мы переходим к поиску take() для всех оставшихся методов в области видимости.Поскольку Iterator находится в области действия, мы действительно находим реализацию перенаправления из стандартной библиотеки и можем прекратить обработку нашего списка типов-кандидатов.

Для второго случая, I: Read, мы также начинаем с &mut I, но поскольку Read находится вне области действия, мы не увидим реализацию пересылки.Однако, как только мы доберемся до I в нашем списке типов кандидатов, включается предложение о методах, предоставляемых границами признаков: они сначала ищутся, независимо от того, находится ли свойство в области видимости.I имеет границу черты Read, поэтому <Read>::take() найдено.Как мы видели выше, вызов этого метода приводит к сообщению об ошибке.

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

0 голосов
/ 26 сентября 2018

impl<'a, I: Iterator + ?Sized> Iterator for &'a mut I - причина, по которой первая функция компилируется.Он реализует Iterator для всех изменяемых ссылок на итераторы.

Черта Read имеет эквивалент , но, в отличие от Iterator, черта Read отсутствует в прелюдия , поэтому вам нужно use std::io::Read, чтобы использовать это значение:

use std::io::Read; // remove this to get "cannot move out of borrowed content" err

fn foo<I, T>(iter: &mut I)
where
    I: std::iter::Iterator<Item = T>,
{
    let _y = iter.take(2);
}

fn bar<I>(iter: &mut I)
where
    I: std::io::Read,
{
    let _y = iter.take(2);
}

Детская площадка

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