跳转到内容

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 上的关联常量)。问题一样,修法也一样。

看看 rustc 标出了什么:

  1. Default::default() 下面的 ^^^^:“cannot call associated function of trait”(不能调用 trait 的关联函数)。default 是属于 Default trait 的函数,而你是通过 trait 的名字调用它的。
  2. help 建议写成 </* self type */ as Default>::default()。/* self type */ 是留给你填的空。rustc 在问:“你说的是哪个类型的 default?”
  3. 在 Shape 的例子里,rustc 用它找到的每个类型(Circle、Square)帮你填好了这个空。如果只有一个类型实现了这个 trait,它就只建议那一个。

关键在这里。trait 是一个承诺:“任何实现了 Default 的类型,都能造出一个自己的初始值”。类型是兑现承诺的那一方:Settings、String、u32 和 Vec<T> 各自用自己的方式兑现它。Default::default() 是在向承诺本身要一个值,可承诺什么也造不出来,只有类型才行。

想象一块“可外带”的牌子。很多餐馆都把它挂在橱窗上。但你没法向这块牌子点晚饭,你得先选一家餐馆。Settings::default() 就是在选餐馆。

大多数 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 只是在编译时就告诉你,还指出了要改的确切位置。

几乎总是选这个。把 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 引入)。

如果你喜欢 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