跳转到内容

Rust 错误 E0424:expected value, found module `self`(在方法外使用 self)

E0424 的意思是:你在一个没有 self 参数的函数里写了 self,所以 Rust 找不到这个“自己”指的是谁。这是个非常常见的小失误,尤其是从 Java、C# 或 JavaScript 转过来的人,因为在那些语言里 this 总是存在。修复通常只要一个词:在函数的参数列表里加上 &self。本页属于 Rust 编译错误详解 系列。

error[E0424]: expected value, found module `self`
--> src/main.rs:7:32
|
6 | fn describe() {
| -------- this function doesn't have a `self` parameter
7 | println!("Player: {}", self.name); // ❌ which player?
| ^^^^ `self` value is a keyword only available in methods with a `self` parameter
|
help: add a `self` receiver parameter to make the associated `fn` a method
|
6 | fn describe(&self) {
| +++++
error[E0599]: no method named `describe` found for struct `Player` in the current scope
--> src/main.rs:13:10
|
13 | hero.describe();
| ^^^^^^^^ this is an associated function, not a method

下面这段程序会产生这个错误:

struct Player {
name: String,
}
impl Player {
fn describe() {
println!("Player: {}", self.name); // ❌ which player?
}
}
fn main() {
let hero = Player { name: String::from("Ferris") };
hero.describe();
}

(注释保留英文,因为 rustc 会把这一行原样打印在错误信息里,意思是“哪个玩家?”。)

第二个错误 E0599 是第一个错误连带出来的。修好 E0424,它也会跟着消失。

看看 rustc 在你的代码上标出的两处:

  1. 第 6 行:fn describe() 的括号是空的。rustc 指出 “this function doesn’t have a self parameter”(这个函数没有 self 参数)。
  2. 第 7 行:self.name 要的是“调用这个函数的那个玩家”的名字。可是 describe 并没有拿到任何玩家,所以 self 在这里什么也不指。

impl 块里没有 self 参数的函数叫作关联函数。它属于类型本身(Player::describe()),而不属于某一个具体的值。有 self 参数的函数才是方法,只有方法里才能写 self。

想象一张表格,上面写着“在这里填上你自己的名字”。只有当有人拿着这张表格时,这句话才有意义。describe() 就是一张没人拿着的表格。

为什么会说 “found module self”(找到的是模块 self)?在 Rust 里,self 还有第二个意思:当前模块,比如 use self::helpers;。没有 self 这个值的时候,rustc 就退回到这个意思,然后抱怨模块不是一个值。“module”这个词可以忽略,真正的问题是缺了参数。

在 Rust 里,没有什么是藏起来的。Java 和 C# 会给每个非 static 方法一个看不见的 this。Rust 则要求你把 self 写成第一个参数,还要说明方法怎样拿到它:只是看看、要修改,还是拿走所有权。这个选择是借用规则的一部分,所以必须写出来。

这样一来,每个读代码的人和编译器都能准确知道方法会对它的值做什么。fn summary(&self) 承诺什么都不改。fn add(&mut self, ...) 提醒你它会改东西。fn checkout(self) 会把这个值用掉。编译器随后会替你检查这些承诺。方法和 impl 块在 结构体 一课里有讲解。

修复 1:加上 &self、&mut self 或 self

Section titled “修复 1:加上 &self、&mut self 或 self”

如果这个函数本来就是要处理某一个已有的值,就选这个办法。大多数时候这都是正确的修复。加上 rustc 建议的参数,再用点号调用它:

struct Player {
name: String,
}
impl Player {
fn describe(&self) {
println!("玩家:{}", self.name);
}
}
fn main() {
let hero = Player { name: String::from("Ferris") };
hero.describe();
}

输出:

玩家:Ferris
▶ 动手试一试

rustc 总是建议 &self,但其实有三种。选和方法要做的事相符的那一种:

参数 这个方法…… 常见名字
&self 只读取这个值 summary、len、is_empty、describe
&mut self 会修改这个值 add、push、reset、level_up
self 会用掉这个值(拿走所有权) into_...、finish、build、checkout

下面是同一个类型上的三种写法:

struct Order {
items: Vec<String>,
}
impl Order {
fn summary(&self) -> String {
format!("{} 件商品:{}", self.items.len(), self.items.join(", "))
}
fn add(&mut self, item: &str) {
self.items.push(item.to_string());
}
fn checkout(self) -> Vec<String> {
self.items
}
}
fn main() {
let mut order = Order { items: Vec::new() };
order.add("coffee");
order.add("bagel");
println!("{}", order.summary());
let packed = order.checkout(); // order 在这里被用掉了
println!("已打包:{:?}", packed);
}

输出:

2 件商品:coffee, bagel
已打包:["coffee", "bagel"]
▶ 动手试一试

如果你选了 &self,方法却修改了某个字段,编译器会马上告诉你(E0594 “cannot assign to self.count, which is behind a & reference”,或者像 push 这样的调用会报 E0596)。改成 &mut self 就好。拿不准的时候,先用 &self;self 是用得最少的那个。

修复 2:写成 Type::method(&value) 来调用

Section titled “修复 2:写成 Type::method(&value) 来调用”

函数一旦有了 self 参数,就有两种调用方式。hero.label() 和 Player::label(&hero) 的意思完全一样:本来写在点号前面的值,变成了第一个参数。大多数时候你会用点号的写法。Type:: 的写法适合把方法本身传给 map 之类的东西:

struct Player {
name: String,
level: u32,
}
impl Player {
fn label(&self) -> String {
format!("{}(等级 {})", self.name, self.level)
}
}
fn main() {
let hero = Player { name: String::from("Ferris"), level: 3 };
println!("{}", hero.label());
println!("{}", Player::label(&hero)); // 同一个调用
let team = vec![hero, Player { name: String::from("Corro"), level: 5 }];
let labels: Vec<String> = team.iter().map(Player::label).collect();
println!("{:?}", labels);
}

输出:

Ferris(等级 3)
Ferris(等级 3)
["Ferris(等级 3)", "Corro(等级 5)"]
▶ 动手试一试

行不通的是不带值的 Player::label():方法总是需要它的 self。漏掉它会得到 E0061,“this function takes 1 argument but 0 arguments were supplied”(这个函数需要 1 个参数,却提供了 0 个)。

修复 3:把函数需要的东西传进去

Section titled “修复 3:把函数需要的东西传进去”

如果还没有值,或者这个函数根本不在 impl 块里,就选这个办法。

经典的例子是构造函数。new 负责创建值,所以没有可以读取的 self。把初始数据作为普通参数传给 new,再用 Self { ... } 构造结构体:

struct Counter {
count: u32,
step: u32,
}
impl Counter {
fn new(step: u32) -> Self {
Self { count: 0, step }
}
fn tick(&mut self) {
self.count += self.step;
}
}
fn main() {
let mut counter = Counter::new(5);
counter.tick();
counter.tick();
println!("计数:{}", counter.count);
}

输出:

计数:10
▶ 动手试一试

注意大小写:Self(大写 S)是类型,所以 Self { ... } 构造出一个新值。self(小写 s)是调用方法的那个值。

同样的思路也能修复自由函数,也就是不在任何 impl 块里的函数。rustc 在那里的说法略有不同:“this function can’t have a self parameter”(这个函数不能有 self 参数)。改成通过引用把结构体传进去:

struct Cart {
items: Vec<u32>,
}
fn total(cart: &Cart) -> u32 {
cart.items.iter().sum()
}
// 调用方式:total(&cart)

或者把 total 移进 impl Cart,像修复 1 那样给它加上 &self。两种都可以;如果这个函数明显是“关于”购物车的,写成方法读起来更自然。

在 new 里面。 在 fn new() -> Self 里写 self.count = 0; 会报 E0424,而 rustc 建议的 fn new(&self) -> Self 根本说不通(你得先有一个计数器,才能造出一个计数器)。修复:像修复 3 那样,用 Self { count: 0 } 构造值。

在关联函数里的闭包中。 问题不在闭包,而在包着它的那个函数:

error[E0424]: expected value, found module `self`
--> src/main.rs:8:35
|
7 | fn sale_prices(prices: &[u32]) -> Vec<u32> {
| ----------- this function doesn't have a `self` parameter
8 | prices.iter().map(|p| p - self.discount).collect()
| ^^^^ `self` value is a keyword only available in methods with a `self` parameter

修复:把它改成方法 fn sale_prices(&self, prices: &[u32]),或者把数字传进去 fn sale_prices(prices: &[u32], discount: u32)。见 闭包。

在 main 或辅助函数里。 self 在 impl 块外面永远用不了。修复:把结构体作为参数传进去(total(&cart)),或者在它上面调用方法(cart.total())。

从 C# 或 Java 的 “static” 方法转过来。 没有 self 的关联函数,就是 Rust 版的静态方法。如果你想要实例方法,就必须自己加上 self 参数。见 面向 C# 开发者的 Rust。

  • E0425:反过来的失误。你在方法里,却把 self.count 写成了 count。
  • E0599:“no method named … found”(找不到名为 …… 的方法)。经常紧跟在 E0424 后面出现,因为没有 self 的函数不能用点号调用。
  • E0061:参数个数不对,比如调用 Player::label() 时没传值。
  • E0596:你加了 &self,但方法会修改字段,所以需要 &mut self。用这种方式给字段赋值会得到 E0594。
  • 所有编译错误:全部错误页面的列表。

小测验

1. 在 impl Player 里,fn heal() { self.hp += 10; } 报 E0424。最好的修复是什么?

2. 为什么 fn new() -> Self 里不能用 self.name?

3. Player 上定义了 fn label(&self) -> String。哪个调用和 hero.label() 一样?

得分: 0 / 3