Почему я должен применять методы по признаку, а не как часть этого признака?

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

Я сделал небольшой эксперимент как с "нормальным" методом признаков, так и с методом, определенным в блоке impl:

trait Foo {
    fn foo_in_trait(&self) { println!("in foo") }
}

impl Foo {
    fn foo_in_impl(&self) { println!("in impl") }
}

impl Foo for u8 {}

fn main() {
    let x = Box::new(42u8) as Box<Foo>;
    x.foo_in_trait();
    x.foo_in_impl();

    let y = &42u8 as &Foo;
    y.foo_in_trait();
    // y.foo_in_impl(); // error: borrowed value does not live long enough
}

Из этого ограниченного эксперимента кажется, что методы, определенные в блоке impl, являются более ограничительными, чем методы, определенные в блоке trait. Вероятно, есть что-то лишнее, что делает это так разблокируется, но я просто не знаю, что это такое! ^ _ ^

Разделы из языка программирования ржавчины на объектах traits и не делают упоминание об этом. Поиск самого источника Rust кажется, что только Any использует эту особенность. Я не видел, чтобы это использовалось в нескольких ящиках, где я смотрел исходный код.

Ответы

Ответ 1

Когда вы определяете черту, Rust также определяет тип объекта-объекта с тем же именем. Объекты Trait имеют параметр lifetime, который обозначает кратчайший из параметров жизненного цикла. Чтобы указать это время жизни, вы пишете тип как Foo + 'a.

Когда вы пишете impl Foo {, вы не указываете этот параметр lifetime, и по умолчанию он равен 'static. Это примечание компилятора в инструкции y.foo_in_impl(); намекает на следующее:

Примечание: ссылка должна быть действительной для статического времени жизни...

Все, что нам нужно сделать, чтобы сделать это более разрешительным, - написать общий impl в течение любого времени жизни:

impl<'a> Foo + 'a {
    fn foo_in_impl(&self) { println!("in impl") }
}

Теперь обратите внимание, что аргумент self на foo_in_impl является заимствованным указателем, который имеет собственный параметр времени жизни. Тип self в его полной форме выглядит как &'b (Foo + 'a) (круглые скобки требуются из-за приоритета оператора). A Box<u8> владеет свойством u8 – он ничего не заимствует –, поэтому вы можете создать &(Foo + 'static) из него. С другой стороны, &42u8 создает &'b (Foo + 'a), где 'a не 'static, потому что 42u8 помещается в скрытую переменную в стеке, а объект-объект заимствует эту переменную. (Однако это не имеет смысла, u8 ничего не берет, поэтому его реализация Foo всегда должна быть совместима с Foo + 'static... тот факт, что 42u8 заимствован из стека, должен влиять 'b, а не 'a.)

Еще одна вещь, которую следует отметить, заключается в том, что методы-признаки являются полиморфными, даже если они имеют реализацию по умолчанию, и они не переопределены, а встроенные методы для объектов-признаков мономорфны (есть только одна функция, независимо от того, что стоит за признаком), Например:

use std::any::TypeId;

trait Foo {
    fn foo_in_trait(&self)
    where
        Self: 'static,
    {
        println!("{:?}", TypeId::of::<Self>());
    }
}

impl Foo {
    fn foo_in_impl(&self) {
        println!("{:?}", TypeId::of::<Self>());
    }
}

impl Foo for u8 {}
impl Foo for u16 {}

fn main() {
    let x = Box::new(42u8) as Box<Foo>;
    x.foo_in_trait();
    x.foo_in_impl();

    let x = Box::new(42u16) as Box<Foo>;
    x.foo_in_trait();
    x.foo_in_impl();
}

Пример вывода:

TypeId { t: 10115067289853930363 }
TypeId { t: 1357119791063736673 }
TypeId { t: 14525050876321463235 }
TypeId { t: 1357119791063736673 }

В методе признаков мы получаем идентификатор типа базового типа (здесь u8 или u16), поэтому мы можем заключить, что тип &self будет варьироваться от одного исполнителя к другому (он '&u8 для u8 исполнитель и &u16 для u16 реализатор – не объект-объект). Однако в встроенном методе мы получаем идентификатор типа Foo, поэтому мы можем заключить, что тип &self всегда &Foo (объект-объект).

Ответ 2

Я подозреваю, что причина очень проста: может быть переопределена или нет?

Метод, реализованный в блоке trait, может быть переопределен разработчиками trait, он просто предоставляет значение по умолчанию.

С другой стороны, метод, реализованный в блоке impl, не может быть переопределен.

Если это рассуждение правильно, то ошибка, которую вы получаете за y.foo_in_impl(), - это просто недостаток полировки: она должна была работать. См. Фрэнсис Ганье более полный ответ на взаимодействие со временем жизни.