Почему я должен реализовывать методы для черты, а не как часть черты?
Пытаясь понять 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()
это просто недостаток лака: это должно было сработать. См. Более полный ответ Фрэнсиса Ганье о взаимодействии с воплощениями.