跳转到内容

Rust 错误 E0615:attempted to take value of method(把方法当字段用)

E0615 的意思是:你写了不带括号的 value.something,可 something 是方法,不是字段。 在 Rust 里,方法只有用 () 调用时才会运行,哪怕它不需要任何参数。如果你学过 C#、JavaScript 或 Python,这个错误会特别常见,因为在那些语言里,.Count、.length 或属性(property)看起来就像普通字段。修法几乎总是只加两个字符。(本页属于 Rust 编译错误详解系列。)

error[E0615]: attempted to take value of method `len` on type `String`
--> src/main.rs:3:46
|
3 | println!("{} has {} letters", name, name.len); // ❌ `len` is a method
| ^^^ method, not a field
|
help: use parentheses to call the method
|
3 | println!("{} has {} letters", name, name.len()); // ❌ `len` is a method
| ++

第一行的意思是“试图取类型 String 上方法 len 的值”。help 建议你“加上括号来调用这个方法”。

产生这个错误的程序是这样的:

fn main() {
let name = String::from("Ferris");
println!("{} has {} letters", name, name.len); // ❌ `len` is a method
}

从后往前读第一行:on type String 是 Rust 查看的类型,method len 是它在那里找到的东西。所以 Rust 确实找到了 len,只是它不是一个能像字段那样读取的值,而是一段需要运行的代码。

标注 method, not a field(是方法,不是字段),以及 help 里 () 下面的 ++,说的正是这件事:加上括号就好了。

可以想象一台自动售货机。字段就像印在机器上的标签(“12 号机”):看一眼就行。方法就像一个按钮(“找零”):不按下去,什么都不会发生。name.len 只是指着按钮,name.len() 才是按下它。

Rust 把字段和方法分得清清楚楚。字段是存放在结构体里的数据。方法是定义在 impl 块里、接收 self 的函数。读字段写 value.field,调用方法写 value.method()。参见用方法添加行为。

很多语言用“属性”模糊了这条界线:C# 的 list.Count 或 JavaScript 的 text.length 看起来像字段,背后却在运行代码。Rust 特意没有属性。看到 (),你就知道有代码在运行,可能要花点时间;没看到,你就知道这只是读取已经存好的数据。没有任何东西被藏起来。

一个结构体甚至可以有同名的字段和方法,比如一个私有的 balance 字段和一个公有的 balance() 方法。之所以能这样,正是因为 Rust 从不猜你指的是哪一个:不带括号就是字段,带括号就是方法。

几乎每次都选这个。照着 help: 那一行调用这个方法。无论是你自己写的方法(比如购物车上的 total),还是 len、is_empty、trim、count 这些标准方法,都是一样的:

struct Item {
price: u32, // 单位:分
qty: u32,
}
struct Cart {
items: Vec<Item>,
}
impl Cart {
fn total(&self) -> u32 {
self.items.iter().map(|i| i.price * i.qty).sum()
}
}
fn main() {
let name = String::from("Ferris");
println!("{} 有 {} 个字母", name, name.len()); // ✅ 调用了
let cart = Cart {
items: vec![Item { price: 250, qty: 2 }, Item { price: 100, qty: 1 }],
};
if !cart.items.is_empty() {
println!("合计:{} 分", cart.total()); // ✅ 调用了
}
}

输出:

Ferris 有 6 个字母
合计:600 分
▶ 动手试一试

name 保留了英文的“Ferris”,因为 len() 数的是字节,中文名字的结果会不一样。如果方法需要参数,rustc 会用占位符 _ 告诉你参数放在哪里。对于 s.contains,help 写的是 s.contains(_):把你要找的内容填进去,比如 s.contains("world")。如果你不确定一个方法需要什么,rustc 接下来的错误(E0061)会告诉你它接收几个参数。

修复 2:把方法当作函数传入,而不是写 value.method

Section titled “修复 2:把方法当作函数传入,而不是写 value.method”

当你并不想马上调用这个方法,而是想把它交给别的东西(比如 .map() 或 sort_by_key)时,选这个。写成 .map(words[0].len) 也会得到 E0615。有两个好办法:写一个调用该方法的简短闭包,或者用方法的完整路径 String::len,它用起来就像一个函数名:

fn main() {
let words = vec![String::from("crab"), String::from("ferris"), String::from("rust")];
// 一个对每个元素调用方法的闭包
let lengths: Vec<usize> = words.iter().map(|w| w.len()).collect();
println!("{:?}", lengths);
// 方法的路径用起来就像函数名
let same: Vec<usize> = words.iter().map(String::len).collect();
println!("{:?}", same);
let mut sorted = words.clone();
sorted.sort_by_key(|w| w.len());
println!("{:?}", sorted);
}

输出:

[4, 6, 4]
[4, 6, 4]
["crab", "rust", "ferris"]
▶ 动手试一试

这里能用 String::len,是因为 iter() 给出的是 &String 元素,而 String::len 接收的正是 &String。如果类型对不太上,闭包 |w| w.len() 是总能行得通的简单选择。

修复 3:如果它确实是存储的数据,就改成字段

Section titled “修复 3:如果它确实是存储的数据,就改成字段”

当错误出在你自己的结构体里,而你意识到这个值本来就该存起来时,选这个。这是个设计问题:用字段还是用方法?

  • 当值只给定一次、不依赖其他任何东西时,用字段:id、name、created_at 日期。
  • 当值是从其他数据算出来的时,用方法:由商品算出的 total、由宽和高算出的 area、由内容算出的 len。

存起来的 total 字段,只要有人加了商品却忘了更新它,就过时了。total() 方法每次都重新计算,所以永远不会出错。下面 id 是字段,total 是方法,你可以看到订单变了,方法给出的结果依然正确:

mod shop {
pub struct Order {
pub id: u32, // 存储的数据:设置一次,用 order.id 读取
prices: Vec<u32>, // 私有:只能通过方法修改
}
impl Order {
pub fn new(id: u32) -> Self {
Order { id, prices: Vec::new() }
}
pub fn add(&mut self, price: u32) {
self.prices.push(price);
}
// 计算出来的:总是和 prices 一致,不会过时
pub fn total(&self) -> u32 {
self.prices.iter().sum()
}
pub fn item_count(&self) -> usize {
self.prices.len()
}
}
}
use shop::Order;
fn main() {
let mut order = Order::new(17);
order.add(250);
order.add(100);
println!("订单 {}:{} 件商品,共 {} 分", order.id, order.item_count(), order.total());
order.add(50);
println!("订单 {}:{} 件商品,共 {} 分", order.id, order.item_count(), order.total());
}

输出:

订单 17:2 件商品,共 350 分
订单 17:3 件商品,共 400 分
▶ 动手试一试

很多库里还能看到一种折中做法:私有字段加上一个同名的公有方法(也就是 getter)。其他代码可以用 acc.balance() 读取这个值,却不能直接修改它。这时在模块外面写 acc.balance,得到的是一个略有不同的错误:E0616“field balance of struct Account is private”(结构体 Account 的字段 balance 是私有的),并提示 a method `balance` also exists, call it with parentheses(还有一个同名方法 balance,加上括号调用它)。修法一样:加上 ()。参见默认一切都是私有的。

从其他语言带来的习惯。 name.len、list.is_empty、text.trim、words.iter().count 都会得到 E0615。它们在 Rust 里全都是方法,所以全都需要 ()。修复:name.len()、list.is_empty(),以此类推。参见常用字符串方法。

Vec 上的 len。 scores.len 是唯一的例外:你得到的是 E0616,“field len of struct Vec is private”(Vec 的字段 len 是私有的),因为 Vec 真的有一个叫 len 的隐藏字段。rustc 仍然会加上 help: a method `len` also exists, call it with parentheses。修复:scores.len()。(scores.is_empty 得到的则是普通的 E0615。)参见 vector。

会修改东西的方法。 单独一行写 counter.increment; 也会得到 E0615,哪怕你并不想要什么值。没有 (),什么都不会运行。修复:counter.increment();。

反过来:把字段当方法调用。 如果 name 是字段,user.name() 会得到 E0599,标注为 field, not a method(是字段,不是方法),并提示 help: remove the arguments(去掉参数)。修复:user.name。如果字段里存的是一个闭包,比如 on_click: Box<dyn Fn() -> String>,rustc 会说 surround the field access with parentheses(用括号把字段访问括起来):写成 (button.on_click)() 来调用它。

小测验

1. 你遇到了“attempted to take value of method is_empty on type String”。该怎么修?

2. 下面哪一个通常应该写成方法,而不是存储的字段?

3. 你想得到每个单词的长度:words.iter().map(???)。哪种写法可行?

得分: 0 / 3