Перейти к содержимому

Ошибка Rust E0790: cannot call associated function on trait (тип не указан)

E0790 значит, что ты попросил у трейта то, что может дать только тип. Default::default() сам по себе — это как сказать «дай значение по умолчанию», не уточнив, значение чего. Ошибка частая, и обычно исправление — поставить впереди имя типа: Settings::default() вместо Default::default(). На этой странице сначала исправления, а потом объяснение разницы между трейтом и типом, чтобы эта ошибка больше тебя не удивляла. Страница — часть справочника по ошибкам компилятора.

error[E0790]: cannot call associated function on trait without specifying the corresponding `impl` type
--> src/main.rs:8:20
|
8 | let settings = Default::default(); // ❌ the default of which type?
| ^^^^^^^^^^^^^^^^^^ cannot call associated function of trait
|
help: use a fully-qualified path to a specific available implementation
|
8 | let settings = </* self type */ as Default>::default(); // ❌ the default of which type?
| +++++++++++++++++++ +

Компилятор говорит: нельзя вызвать ассоциированную функцию трейта, не указав тип с соответствующим impl. Вот программа, которая выдаёт эту ошибку (комментарий значит «значение по умолчанию — какого типа?»):

#[derive(Debug, Default)]
struct Settings {
volume: u8,
dark_mode: bool,
}
fn main() {
let settings = Default::default(); // ❌ the default of which type?
println!("{:?}", settings);
}

Та же ошибка будет, если вызвать функцию твоего собственного трейта через имя трейта, например Shape::new(). Здесь rustc видит два типа, которые реализуют Shape, и предлагает оба варианта:

error[E0790]: cannot call associated function on trait without specifying the corresponding `impl` type
--> src/main.rs:33:17
|
2 | fn new() -> Self;
| ----------------- `Shape::new` defined here
...
33 | let shape = Shape::new();
| ^^^^^^^^^^^^ cannot call associated function of trait
|
help: use a fully-qualified path to one of the available implementations
|
33 | let shape = <Circle as Shape>::new();
| ++++++++++ +
33 | let shape = <Square as Shape>::new();
| ++++++++++ +

Для константы трейта вроде Plugin::ID заголовок немного другой: cannot refer to the associated constant on trait without specifying the corresponding `impl` type. Проблема та же, исправления те же.

Посмотри, что отмечает rustc:

  1. ^^^^ под Default::default(): «cannot call associated function of trait» (нельзя вызвать ассоциированную функцию трейта). default — функция, которая принадлежит трейту Default, а ты вызвал её через имя трейта.
  2. Подсказка (help) предлагает </* self type */ as Default>::default(). Часть /* self type */ — пропуск, который должен заполнить ты. rustc спрашивает: «default какого типа ты имеешь в виду?»
  3. В примере с Shape rustc сам заполняет пропуск каждым найденным типом (Circle, Square). Если трейт реализует только один тип, он предлагает только его.

Вот главная мысль. Трейт — это обещание: «любой тип, который реализует Default, умеет создать своё начальное значение». Тип — это тот, кто обещание выполняет: Settings, String, u32 и Vec<T> выполняют его каждый по-своему. Default::default() просит значение у самого обещания, а обещание ничего создать не может. Может только тип.

Представь табличку «Еда навынос» на окне. Такую табличку вешают многие рестораны. Но заказать ужин у таблички нельзя — нужно выбрать ресторан. Settings::default() — это и есть выбор ресторана.

Большинство методов трейтов принимают self, например circle.area(). Они никогда не вызывают E0790, потому что значение подсказывает Rust тип: circle — это Circle, значит, вызывается area у Circle. Подробнее — в разделе Объявляем трейт.

У некоторых функций трейтов нет self: конструкторы вроде new() и default(), преобразования вроде From::from и константы трейтов вроде ID. Раз смотреть не на что, у Rust есть только два места, откуда узнать тип: имя, которое ты пишешь впереди (Settings::), или тип, который должен получиться в результате (let s: Settings = ...). Если нет ни того, ни другого, Rust не может угадать. Default реализуют многие типы, и выбор наугад тихо изменил бы то, что делает твоя программа.

Это то же самое, что интерфейс в Java или C#. Нельзя вызвать new у интерфейса — нужен класс, который его реализует. Rust просто сообщает об этом во время компиляции и показывает точное место для исправления.

Это решение подходит почти всегда. Замени имя трейта на имя типа. Это самое короткое исправление, и оно хорошо читается: «Settings по умолчанию», «новый Circle»:

#[derive(Debug, Default)]
struct Settings {
volume: u8,
dark_mode: bool,
}
trait Shape {
fn new() -> Self;
fn name() -> String;
}
struct Circle {
radius: f64,
}
impl Shape for Circle {
fn new() -> Self {
Circle { radius: 1.0 }
}
fn name() -> String {
String::from("круг")
}
}
fn main() {
let settings = Settings::default(); // вместо Default::default()
let shape = Circle::new(); // вместо Shape::new()
println!("{:?}", settings);
println!("{} с радиусом {}", Circle::name(), shape.radius);
}

Вывод:

Settings { volume: 0, dark_mode: false }
круг с радиусом 1
▶ Попробуй сам

Circle::new() работает, хотя new объявлена в трейте, а не в блоке impl Circle. Rust ищет new у Circle, находит её через impl Shape for Circle и вызывает. Трейт должен быть в области видимости (объявлен в том же файле или подключён через use).

Выбирай этот вариант, если тебе нравится стиль Default::default() или имя типа длинное. Напиши тип у переменной, и Rust по нему поймёт, какой default вызвать:

#[derive(Debug, Default)]
struct Settings {
volume: u8,
dark_mode: bool,
}
fn main() {
let settings: Settings = Default::default();
let small: u8 = 42;
let big: u32 = From::from(small);
println!("{:?}", settings);
println!("{}", big);
}

Вывод:

Settings { volume: 0, dark_mode: false }
42
▶ Попробуй сам

Это работает, только когда функция возвращает сам тип (Self), как default(), new() и from(). Для fn name() -> String это не работает: знание, что ответ — String, ничего не говорит Rust о том, какую фигуру ты имеешь в виду. Для таких функций используй исправление 1 или 3. Подробнее об аннотациях — в разделе Аннотации типов.

Исправление 3: полный синтаксис <Type as Trait>::function()

Заголовок раздела «Исправление 3: полный синтаксис <Type as Trait>::function()»

Это форма «скажи всё», которую предлагает rustc. Читай <Circle as Shape>::new() как «тот new, который есть у Circle, потому что он реализует Shape». Она работает всегда, но Circle::new() читать проще, так что нужна она, только когда короткая форма неоднозначна:

  • тип реализует два трейта с функцией одного и того же имени, или
  • у типа есть своя функция с таким именем, а тебе нужна версия из трейта.

Здесь Sale можно записать как строку CSV или как печатный отчёт, и у обоих трейтов есть header(). Sale::header() даёт E0034 (на английском) «multiple applicable items in scope», поэтому ты говоришь, какой из них нужен:

trait Csv {
fn header() -> String;
}
trait Report {
fn header() -> String;
}
struct Sale;
impl Csv for Sale {
fn header() -> String { String::from("date,amount") }
}
impl Report for Sale {
fn header() -> String { String::from("Продажи за месяц") }
}
fn main() {
println!("{}", <Sale as Csv>::header());
println!("{}", <Sale as Report>::header());
}

Вывод:

date,amount
Продажи за месяц
▶ Попробуй сам

В уроке о продвинутых трейтах, в разделе Полный синтаксис: когда имена совпадают, есть ещё один пример — в том числе с методами, которые принимают &self.

Исправление 4: в обобщённом коде используй T:: и турборыбу

Заголовок раздела «Исправление 4: в обобщённом коде используй T:: и турборыбу»

Выбирай этот вариант, когда пишешь обобщённую функцию. Внутри fn register<T: Plugin>() тип — это T, так что вызывай T::name() и T::ID, а не Plugin::name(). При вызове функции передай тип через «турборыбу» (turbofish) ::<Type>, потому что нет аргумента, из которого Rust мог бы его узнать:

trait Plugin {
const ID: u32;
fn name() -> String;
}
struct Logger;
struct Backup;
impl Plugin for Logger {
const ID: u32 = 7;
fn name() -> String { String::from("logger") }
}
impl Plugin for Backup {
const ID: u32 = 12;
fn name() -> String { String::from("backup") }
}
fn register<T: Plugin>() -> String {
format!("Плагин №{}: {}", T::ID, T::name())
}
fn main() {
println!("{}", register::<Logger>());
println!("{}", register::<Backup>());
}

Вывод:

Плагин №7: logger
Плагин №12: backup
▶ Попробуй сам

Если убрать турборыбу и написать register(), получишь E0283: «cannot infer type of the type parameter T» (не удаётся вывести тип для параметра T). Здесь T — тоже тип, поэтому T:: работает так же, как Circle:: в исправлении 1. Подробнее о параметрах типов — в уроке Обобщения.

Default::default() без единой подсказки. let config = Default::default(); не компилируется, но тот же вызов работает везде, где тип уже известен: в поле структуры, в аргументе функции или в синтаксисе «заполни остальное»:

let settings = Settings { volume: 8, ..Default::default() }; // OK: Rust знает, что это Settings

Исправь строку с ошибкой через Settings::default() или let config: Settings = Default::default();. Смотри Default: разумное начальное значение.

Внутри обобщённой функции. Имя трейта вместо T:

error[E0790]: cannot call associated function on trait without specifying the corresponding `impl` type
--> src/main.rs:9:29
|
2 | fn name() -> String;
| -------------------- `Shape::name` defined here
...
9 | format!("This is a {}", Shape::name())
| ^^^^^^^^^^^^^ cannot call associated function of trait
|
help: use the fully-qualified path to the only available implementation
|
9 | format!("This is a {}", <Circle as Shape>::name())
| ++++++++++ +

Осторожно: rustc предлагает <Circle as Shape>, потому что пока Circle — единственная фигура. Это скомпилируется, но тогда функция будет всегда говорить «circle», каким бы ни был T. Правильное исправление — T::name(), как в исправлении 4.

Константы трейтов. println!("{}", Plugin::ID); просит число у обещания, а число есть только у каждого конкретного плагина. Исправление: Logger::ID или T::ID в обобщённом коде.

From::from(x) без цели. let total = From::from(42u8); даёт E0790, потому что из u8 можно сделать много типов. Исправление: u32::from(42u8) или let total: u32 = 42u8.into();. Смотри From и Into: преобразования, которые работают всегда.

  • E0282: нужна аннотация типа. Если ты потом используешь значение, например let c = Shape::new();, а затем c.area(), rustc вместо этой ошибки сообщит E0282. Исправления те же.
  • E0283: нужна аннотация типа, когда подходит много типов, например Into::into(n) без цели или обобщённая функция, вызванная без турборыбы.
  • E0599: функция не найдена. Settings::default() даёт её, если Settings не реализует Default; добавь #[derive(Default)]. С let s: Settings = Default::default(); та же ошибка проявится как E0277.
  • E0424: self используется в функции без параметра self. Тоже про функции, которые принадлежат типу, а не конкретному значению.
  • Все ошибки компилятора: полный список страниц об ошибках.

Проверь себя

1. let opts = Default::default(); даёт E0790. Options выводит (derive) Default. Какая строка это исправляет?

2. Почему let s: Settings = Default::default(); компилируется, а let n: String = Shape::name(); всё равно даёт E0790?

3. Как внутри fn show<T: Shape>() вызвать функцию трейта name()?

Результат: 0 / 3