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` parameter7 | 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,它也会跟着消失。
这个错误是什么意思
Section titled “这个错误是什么意思”看看 rustc 在你的代码上标出的两处:
- 第 6 行:
fn describe()的括号是空的。rustc 指出 “this function doesn’t have aselfparameter”(这个函数没有self参数)。 - 第 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 为什么要这样做
Section titled “Rust 为什么要这样做”在 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();}输出:
玩家:Ferrisrustc 总是建议 &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` parameter8 | 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
- E0424 — Rust error codes index(英文):这个错误的官方说明,附一个简短示例。
- 方法语法 —《Rust 程序设计语言》:
&self、&mut self和self,以及new这样的关联函数。 - 方法 —《通过例子学 Rust》:把关联函数和方法放在一起对比的可运行示例。
- The
selfkeyword — 标准库文档(英文):self的两个意思:方法的接收者和当前模块。 - Associated items — The Rust Reference(英文):方法、接收者和关联函数的准确规则。