Ошибка 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:
^^^^подDefault::default(): «cannot call associated function of trait» (нельзя вызвать ассоциированную функцию трейта).default— функция, которая принадлежит трейтуDefault, а ты вызвал её через имя трейта.- Подсказка (help) предлагает
</* self type */ as Default>::default(). Часть/* self type */— пропуск, который должен заполнить ты. rustc спрашивает: «defaultкакого типа ты имеешь в виду?» - В примере с
Shaperustc сам заполняет пропуск каждым найденным типом (Circle,Square). Если трейт реализует только один тип, он предлагает только его.
Вот главная мысль. Трейт — это обещание: «любой тип, который реализует Default, умеет создать своё начальное значение». Тип — это тот, кто обещание выполняет: Settings, String, u32 и Vec<T> выполняют его каждый по-своему. Default::default() просит значение у самого обещания, а обещание ничего создать не может. Может только тип.
Представь табличку «Еда навынос» на окне. Такую табличку вешают многие рестораны. Но заказать ужин у таблички нельзя — нужно выбрать ресторан. Settings::default() — это и есть выбор ресторана.
Почему Rust так делает
Заголовок раздела «Почему Rust так делает»Большинство методов трейтов принимают 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 просто сообщает об этом во время компиляции и показывает точное место для исправления.
Как исправить
Заголовок раздела «Как исправить»Исправление 1: вызови функцию у типа
Заголовок раздела «Исправление 1: вызови функцию у типа»Это решение подходит почти всегда. Замени имя трейта на имя типа. Это самое короткое исправление, и оно хорошо читается: «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 }круг с радиусом 1Circle::new() работает, хотя new объявлена в трейте, а не в блоке impl Circle. Rust ищет new у Circle, находит её через impl Shape for Circle и вызывает. Трейт должен быть в области видимости (объявлен в том же файле или подключён через use).
Исправление 2: укажи тип переменной
Заголовок раздела «Исправление 2: укажи тип переменной»Выбирай этот вариант, если тебе нравится стиль 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
Что почитать
Заголовок раздела «Что почитать»- E0790 — Rust error codes index (на английском): официальное объяснение этой ошибки с коротким примером и исправлением.
- Трейты: определение разделяемого поведения — книга «Язык программирования Rust» (перевод на русский): что такое трейт, как его реализовать и реализации по умолчанию.
- Продвинутые типажи — книга «Язык программирования Rust» (перевод на русский): там есть раздел о полном синтаксисе с классическим примером
<Dog as Animal>::baby_name(). - Устранение неоднозначности в перекрывающихся трейтах — Rust на примерах (перевод на русский): короткий запускаемый пример выбора между двумя трейтами с методами одного имени.
Default— документация стандартной библиотеки (на английском): как выводитьDefault, реализовывать его вручную и пользоваться приёмом..Default::default().