Rust 错误 E0790:cannot call associated function on trait(没有指明类型)
E0790 的意思是:你向一个 trait 要了一样只有类型才能给你的东西。单独写 Default::default(),就像说“给我默认值”,却没说是什么东西的默认值。这个错误很常见,修复通常就是在前面写上类型名:用 Settings::default() 代替 Default::default()。本页先给出修复方法,再讲清楚 trait 和类型的区别,让这个错误不再让你意外。它是编译错误指南的一部分。
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 类型,就不能在 trait 上调用关联函数。
产生这个错误的程序是这样的:
#[derive(Debug, Default)]struct Settings { volume: u8, dark_mode: bool,}
fn main() { let settings = Default::default(); // ❌ the default of which type? println!("{:?}", settings);}如果你通过 trait 名调用自己定义的 trait 里的函数,比如 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 这样的 trait 常量,标题会稍有不同:cannot refer to the associated constant on trait without specifying the corresponding `impl` type(没有指明对应的 impl 类型,就不能引用 trait 上的关联常量)。问题一样,修法也一样。
这个错误是什么意思
Section titled “这个错误是什么意思”看看 rustc 标出了什么:
Default::default()下面的^^^^:“cannot call associated function of trait”(不能调用 trait 的关联函数)。default是属于Defaulttrait 的函数,而你是通过 trait 的名字调用它的。- help 建议写成
</* self type */ as Default>::default()。/* self type */是留给你填的空。rustc 在问:“你说的是哪个类型的default?” - 在
Shape的例子里,rustc 用它找到的每个类型(Circle、Square)帮你填好了这个空。如果只有一个类型实现了这个 trait,它就只建议那一个。
关键在这里。trait 是一个承诺:“任何实现了 Default 的类型,都能造出一个自己的初始值”。类型是兑现承诺的那一方:Settings、String、u32 和 Vec<T> 各自用自己的方式兑现它。Default::default() 是在向承诺本身要一个值,可承诺什么也造不出来,只有类型才行。
想象一块“可外带”的牌子。很多餐馆都把它挂在橱窗上。但你没法向这块牌子点晚饭,你得先选一家餐馆。Settings::default() 就是在选餐馆。
Rust 为什么要这样做
Section titled “Rust 为什么要这样做”大多数 trait 方法都接收 self,比如 circle.area()。它们永远不会引发 E0790,因为值本身告诉了 Rust 类型:circle 是一个 Circle,所以调用的是 Circle 的 area。更多内容见 trait。
有些 trait 函数没有 self:像 new() 和 default() 这样的构造函数,像 From::from 这样的转换函数,以及像 ID 这样的 trait 常量。没有值可看,Rust 只有两个地方能得知类型:你写在前面的名字(Settings::),或者结果必须具有的类型(let s: Settings = ...)。如果两者都没有,Rust 就没法猜。实现了 Default 的类型有很多,随便挑一个会悄悄改变程序的行为。
这和 Java 或 C# 里的接口是一回事。你不能对一个接口调用 new,需要一个实现了它的类。Rust 只是在编译时就告诉你,还指出了要改的确切位置。
修复 1:在类型上调用
Section titled “修复 1:在类型上调用”几乎总是选这个。把 trait 名换成类型名。这是最短的修法,读起来也顺:“默认的 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即使 new 定义在 trait 里,而不是 impl Circle 块里,Circle::new() 也能用。Rust 会在 Circle 上找 new,通过 impl Shape for Circle 找到它,然后调用。前提是这个 trait 在作用域内(定义在同一个文件里,或者用 use 引入)。
修复 2:给变量加上类型注解
Section titled “修复 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()
Section titled “修复 3:完全限定语法 <Type as Trait>::function()”这是 rustc 建议的“把话说全”的写法。<Circle as Shape>::new() 可以读作“Circle 因为实现了 Shape 而拥有的那个 new”。它总是能用,但 Circle::new() 更好读,所以只有在简短写法有歧义时,你才需要它:
- 这个类型实现了两个 trait,而它们都有同名的函数;或者
- 这个类型自己有一个同名的函数,而你想要的是 trait 里的那个版本。
下面的 Sale 既可以写成一行 CSV,也可以写成一份打印的报告,两个 trait 都有 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月度销售报告高级 trait这一课还有另一个例子,里面也有接收 &self 的方法。
修复 4:在泛型代码里用 T:: 和 turbofish
Section titled “修复 4:在泛型代码里用 T:: 和 turbofish”编写泛型函数时就选这个。在 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如果省掉 turbofish,只写 register(),就会得到 E0283:“cannot infer type of the type parameter T”(无法推断类型参数 T 的类型)。这里的 T 也是一个类型,所以 T:: 的用法和修复 1 里的 Circle:: 一样。类型参数的更多内容,见泛型。
Default::default() 没有任何线索。 let config = Default::default(); 会失败,但只要类型已经确定,同样的调用就能用:在结构体字段里、在函数参数里,或者在“其余字段用默认值填上”的语法里:
let settings = Settings { volume: 8, ..Default::default() }; // 没问题:Rust 知道这是 Settings失败的那一行,用 Settings::default() 或 let config: Settings = Default::default(); 来修复。参见常用 trait。
在泛型函数里。 写了 trait 名,而不是 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 这一种图形。这样能编译,但不管 T 是什么,这个函数都会永远说“circle”。正确的修法是 T::name(),就像修复 4 那样。
trait 常量。 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();。参见类型转换。
- E0282:type annotations needed(需要类型注解)。如果之后用到了这个值,比如
let c = Shape::new();后面跟着c.area(),rustc 会改报 E0282。修法相同。 - E0283:有很多类型都符合时需要类型注解,比如没有目标类型的
Into::into(n),或者调用泛型函数时没写 turbofish。 - 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>() 里,应该怎样调用 trait 函数 name()?
得分: 0 / 3
- E0790 — Rust error codes index(英文):这个错误的官方解释,附有一个简短的示例和修法。
- Trait:定义共同行为 — Rust 程序设计语言:什么是 trait、怎样实现 trait,以及默认实现。
- 高级 trait — Rust 程序设计语言:包含讲解完全限定语法的小节,附有经典的
<Dog as Animal>::baby_name()示例。 - 消除重叠 trait — 通过例子学 Rust:一个简短的可运行示例,在两个都有同名方法的 trait 之间做选择。
Default— 标准库文档(英文):deriveDefault、手动实现它,以及..Default::default()写法。