Почему явное разыменование требуется в (* x) .into (), а не в x.my_into ()? - PullRequest
9 голосов
/ 24 сентября 2019

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

Пример выглядит следующим образом.

#[derive(Clone, Copy, Debug)]
struct Foo();

impl Into<&'static str> for Foo {
    fn into(self) -> &'static str {
        "<Foo as Into>::into"
    }
}

fn vec_into<F: Copy + Into<T>, T>(slice: &[F]) -> Vec<T> {
    slice.iter().map(|x| (*x).into()).collect()
}

fn main() {
    let array = [Foo(), Foo(), Foo()];
    let vec = vec_into::<_, &'static str>(&array);
    println!("{:?}", vec);
}

Приведенный выше код работает, но я подумал, что явная разыменование (*x).into() в функции vec_into не нужно.Я считаю, что, поскольку x: &Foo, тогда x.into() будет пытаться найти методы, принимающие тип &Foo, &&Foo, &mut &Foo, Foo, &Foo, &mut Foo.

* 1025.* Это потому, что есть цепочка разыменования &FooFoo, и для каждого U в этой цепочке мы также вставляем &U и &mut U.

Моя интуиция подтверждаетсяТот факт, что следующий код также работает, без какой-либо явной разыменования.

#[derive(Clone, Copy, Debug)]
struct Foo();

trait MyInto<T> {
    fn my_into(self) -> T;
}

impl MyInto<&'static str> for Foo {
    fn my_into(self) -> &'static str {
        "<Foo as MyInto>::my_into"
    }
}

fn vec_my_into<F: Copy + MyInto<T>, T>(slice: &[F]) -> Vec<T> {
    slice.iter().map(|x| x.my_into()).collect()
}

fn main() {
    let array = [Foo(), Foo(), Foo()];
    let my_vec = vec_my_into(&array);
    println!("{:?}", my_vec);
}

Здесь x: &Foo неявно разыменовывается для вызова метода <Foo as MyInto<&'static str>>::my_into.

Меньший пример

Учитывая приведенные выше определения Foo и MyInto, код

let result: &str = (&Foo()).my_into()

работает, но

let result: &str = (&Foo()).into()

не компилируется с ошибкой

error[E0277]: the trait bound `&str: std::convert::From<&Foo>` is not satisfied
  --> src/bin/into.rs:34:33
   |
34 |     let result: &str = (&Foo()).into();
   |                                 ^^^^ the trait `std::convert::From<&Foo>` is not implemented for `&str`
   |
   = note: required because of the requirements on the impl of `std::convert::Into<&str>` for `&Foo`

Ответы [ 2 ]

4 голосов
/ 25 сентября 2019

Rust выполняет поиск метода точно так, как вы его описываете, и сразу находит кандидата для .into() - полной реализации

impl<T, U> Into<U> for T
where
    U: From<T>,
{
    fn into(self) -> U {
        U::from(self)
    }
}

Эта реализация удовлетворяет всем требованиям для методов-кандидатов- он виден в области видимости и определен для типа &Foo, поскольку он определен для любого типа T.Как только компилятор выбрал этого кандидата, он замечает, что границы черты U не выполнены, и выдает ошибку, которую вы видите.

Ситуация для MyInto совершенно другая, потому что вы этого не делаетепредоставить общую реализацию, основанную на From.Если вы это сделаете, вы получите ту же ошибку.

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

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

Интересно, что после проверки вывода компилятора может показаться, что реализация Into на самом деле просто вызывает метод черты From.То, что он ищет, это std::collections::From<&Foo> for &str.Поэтому, если мы реализуем эту черту, компилятор действительно найдет нашу функцию и выполнит ее.

Используя следующие объявления:

#[derive(Clone, Copy, Debug)]
struct Foo();

impl std::convert::From<&Foo> for &str {
    fn from(f: &Foo) -> &'static str {
        "A &str"// This could be replaced with an actual implementation
    }
}

Ваш код работает так, как вы хотели:

let result: &str = (&Foo()).into();// No compiler errors.

Исходный код, который вы хотели, работает, и его на самом деле нетрудно реализовать.

...