Разрешение неоднозначности между методами с одинаковым именем
В Rust ничего не мешает трейту иметь метод с тем же именем, что и метод другого трейта, и Rust не препятствует реализации обоих трейтов для одного типа. Также можно реализовать метод непосредственно на типе с тем же именем, что и методы из трейтов.
При вызове методов с одинаковым именем вам нужно указать Rust, какой именно метод вы хотите использовать. Рассмотрим код в Listing 19-16, где мы определили два трейта, Pilot
и Wizard
, которые оба имеют метод с именем fly
. Затем мы реализуем оба трейта для типа Human
, для которого уже реализован метод с именем fly
. Каждый метод fly
делает что-то другое.
Filename: src/main.rs
trait Pilot {
fn fly(&self);
}
trait Wizard {
fn fly(&self);
}
struct Human;
impl Pilot for Human {
fn fly(&self) {
println!("This is your captain speaking.");
}
}
impl Wizard for Human {
fn fly(&self) {
println!("Up!");
}
}
impl Human {
fn fly(&self) {
println!("*waving arms furiously*");
}
}
Listing 19-16: Определены два трейта, имеющие метод fly
, и реализованы для типа Human
, а также непосредственно на Human
реализован метод fly
.
Когда мы вызываем fly
для экземпляра Human
, компилятор по умолчанию вызывает метод, непосредственно реализованный на типе, как показано в Listing 19-17.
Filename: src/main.rs
fn main() {
let person = Human;
person.fly();
}
Listing 19-17: Вызов fly
для экземпляра Human
Запуск этого кода выведет *waving arms furiously*
, что показывает, что Rust вызвал метод fly
, непосредственно реализованный для Human
.
Для вызова методов fly
из трейта Pilot
или трейта Wizard
нам нужно использовать более явный синтаксис, чтобы указать, какой именно метод fly
мы имеем в виду. Listing 19-18 демонстрирует этот синтаксис.
Filename: src/main.rs
fn main() {
let person = Human;
Pilot::fly(&person);
Wizard::fly(&person);
person.fly();
}
Listing 19-18: Указание, какой трейтский метод fly
мы хотим вызвать
Указание имени трейта перед именем метода делает понятным для Rust, какой реализацию fly
мы хотим вызвать. Мы также могли бы написать Human::fly(&person)
, что эквивалентно person.fly()
, которое мы использовали в Listing 19-18, но это немного длиннее для написания, если нам не нужно разрешить неоднозначность.
Запуск этого кода выводит следующее:
This is your captain speaking.
Up!
*waving arms furiously*
Поскольку метод fly
принимает параметр self
, если бы у нас были два типа, которые оба реализуют один трейт, Rust мог бы определить, какую реализацию трейта использовать на основе типа self
.
Однако ассоциированные функции, которые не являются методами, не имеют параметра self
. Когда есть несколько типов или трейтов, которые определяют не-методные функции с одинаковым именем функции, Rust не всегда знает, какой тип вы имеете в виду, если вы не используете полный квалифицированный синтаксис. Например, в Listing 19-19 мы создаем трейт для приютов животных, которые хотят именовать всех щенят на Spot. Мы создаем трейт Animal
с ассоциированной не-методной функцией baby_name
. Трейт Animal
реализуется для структуры Dog
, для которой мы также непосредственно предоставляем ассоциированную не-методную функцию baby_name
.
Filename: src/main.rs
trait Animal {
fn baby_name() -> String;
}
struct Dog;
impl Dog {
fn baby_name() -> String {
String::from("Spot")
}
}
impl Animal for Dog {
fn baby_name() -> String {
String::from("puppy")
}
}
fn main() {
println!("A baby dog is called a {}", Dog::baby_name());
}
Listing 19-19: Трейт с ассоциированной функцией и тип с ассоциированной функцией с тем же именем, который также реализует трейт
Мы реализуем код для именования всех щенят Spot в ассоциированной функции baby_name
, определенной для Dog
. Тип Dog
также реализует трейт Animal
, который описывает характеристики, общие для всех животных. Щенят называются щенками, и это выражается в реализации трейта Animal
для Dog
в функции baby_name
, связанной с треитом Animal
.
В main
мы вызываем функцию Dog::baby_name
, которая вызывает ассоциированную функцию, определенную непосредственно для Dog
. Этот код выводит следующее:
A baby dog is called a Spot
Этот вывод не соответствует нашим ожиданиям. Мы хотим вызвать функцию baby_name
, которая является частью трейта Animal
, который мы реализовали для Dog
, чтобы код вывел A baby dog is called a puppy
. Техника, которую мы использовали в Listing 19-18 для указания имени трейта, здесь не поможет; если мы изменим main
на код из Listing 19-20, мы получим ошибку компиляции.
Filename: src/main.rs
fn main() {
println!("A baby dog is called a {}", Animal::baby_name());
}
Listing 19-20: Попытка вызвать функцию baby_name
из трейта Animal
, но Rust не знает, какую реализацию использовать
Поскольку Animal::baby_name
не имеет параметра self
, и могут быть другие типы, которые реализуют трейт Animal
, Rust не может определить, какую реализацию Animal::baby_name
мы хотим. Мы получим эту ошибку компилятора:
error[E0283]: type annotations needed
--> src/main.rs:20:43
|
20 | println!("A baby dog is called a {}", Animal::baby_name());
| ^^^^^^^^^^^^^^^^^ cannot infer
type
|
= note: cannot satisfy `_: Animal`
Для разрешения неоднозначности и указания Rust, что мы хотим использовать реализацию Animal
для Dog
по сравнению с реализацией Animal
для какого-то другого типа, нам нужно использовать полный квалифицированный синтаксис. Listing 19-21 демонстрирует, как использовать полный квалифицированный синтаксис.
Filename: src/main.rs
fn main() {
println!(
"A baby dog is called a {}",
<Dog as Animal>::baby_name()
);
}
Listing 19-21: Использование полного квалифицированного синтаксиса для указания, что мы хотим вызвать функцию baby_name
из трейта Animal
, реализованного для Dog
Мы предоставляем Rust аннотацию типа внутри угловых скобок, которая показывает, что мы хотим вызвать метод baby_name
из трейта Animal
, реализованного для Dog
, сказав, что мы хотим рассматривать тип Dog
как Animal
для этого вызова функции. Теперь этот код выведет то, что мы хотим:
A baby dog is called a puppy
В общем, полный квалифицированный синтаксис определяется следующим образом:
<Type as Trait>::function(receiver_if_method, next_arg,...);
Для ассоциированных функций, которые не являются методами, не будет receiver
: будет только список других аргументов. Вы можете использовать полный квалифицированный синтаксис везде, где вы вызываете функции или методы. Однако вы можете опустить любую часть этого синтаксиса, которую Rust может определить из других сведений в программе. Вам нужно использовать этот более подробный синтаксис только в тех случаях, когда есть несколько реализаций, использующих одно и то же имя, и Rust нуждается в помощи для идентификации той реализации, которую вы хотите вызвать.