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

Пытаясь понять 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 блок. Вполне вероятно, что есть что-то дополнительное, что делает это таким образом, открывает, но я просто еще не знаю, что это такое! ^_^

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

2 ответа

Решение

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

Когда ты пишешь impl Foo {, вы не указываете этот параметр времени жизни, и по умолчанию он '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) (скобки требуются из-за приоритета оператора). Box<u8> владеет своим u8 - это ничего не заимствует - так что вы можете создать &(Foo + 'static) из этого. С другой стороны, &42u8 создает &'b (Foo + 'a) где 'a не является 'static, так как 42u8 помещается в скрытую переменную в стеке, и объект-черта заимствует эту переменную. (Это на самом деле не имеет смысла; u8 ничего не заимствует, поэтому его Foo реализация всегда должна быть совместима с Foo + 'static... дело в том, что 42u8 заимствовано из стека должно влиять 'bне 'a.)

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

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 }

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

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

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

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

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

Другие вопросы по тегам