跳转到内容

Rust 错误 E0610:primitive type doesn't have fields(基本类型没有字段)

E0610 的意思是:你写了 something.field,可 something 只是一个普通的数字(或者 bool、char),而这些类型根本没有字段。 这几乎总是说明,这个变量并不是你以为的那种东西:也许你之前已经把数字从结构体里取出来了,也许某个函数返回的是数字而不是结构体,又或者你多写了一个 .0。错误信息会告诉你 Rust 看到的确切类型,所以离修好只差一步。这个系列的其他错误,请看 Rust 编译错误详解。

error[E0610]: `{integer}` is a primitive type and therefore doesn't have fields
--> src/main.rs:3:22
|
3 | println!("{}", x.value); // ❌ x 是数字,没有 `value` 字段
| ^^^^^

第一行的意思是“{integer} 是基本类型,所以没有字段”。

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

fn main() {
let x = 5;
println!("{}", x.value); // ❌ x 是数字,没有 `value` 字段
}

反引号里的类型,就是 Rust 认为你的变量是什么。这里是 `{integer}`,意思是“某种整数,具体是哪一种我还没定”(如果没有别的线索,Rust 最后会选 i32)。你还可能看到表示小数的 `{float}`,或者 u32、usize、f64、bool、char 这样的具体类型。

^^^^^ 指的是点号后面的名字。Rust 并不是在说“value 拼错了”,而是在说“数字不可能有任何字段,不管你叫它什么”。

所以要问的问题是:这个变量为什么是一个数字? 这就像去找“数字 5 上的价格标签”。商品才有价格标签,5 只是一个数字而已。

i32、u32、f64、bool、char 这些基本类型(primitive type)是语言内置的单个值。它们不是结构体,里面没有任何可以用“点号 + 名字”取出来的东西。参见数据类型。

不过它们有方法:x.abs()、x.pow(2)、price.round()、c.is_alphabetic()。调用方法要写 ()。字段则是存放在结构体里的数据,只有结构体、元组和联合体(union)才有字段。

因为 Rust 在编译时就会检查每一个 .field,这种混淆永远不会变成运行时的崩溃,也不会悄悄变成一个 undefined。你马上就能知道,而且精确到哪一行。

当你之前已经把字段从结构体里取出来时,选这个方法。写完 let count = counter.value; 之后,count 本身就是那个数字了,所以 count.value 等于是在 u32 上找字段:

error[E0610]: `u32` is a primitive type and therefore doesn't have fields
--> src/main.rs:8:26
|
8 | println!("{}", count.value);
| ^^^^^

要么直接使用这个数字,要么回到结构体,从那里读取字段:

struct Counter {
value: u32,
}
fn main() {
let mut counter = Counter { value: 0 };
counter.value += 3;
let count = counter.value; // 现在 count 只是一个 u32
println!("{}", count); // ✅ 直接用这个数字
println!("{}", counter.value); // ✅ 或者从结构体里读取字段
}

输出:

3
3
▶ 动手试一试

不确定一个变量是什么类型?在编辑器里把鼠标悬停在它上面就行(需要 rust-analyzer,参见 Rust 开发工具)。或者临时写一行 let _: () = count;:rustc 会回答 expected `()`, found `usize`,告诉你真正的类型。看完记得把这一行删掉。

修复 2:返回或存放结构体,而不是数字

Section titled “修复 2:返回或存放结构体,而不是数字”

当这个值来自某个函数或集合,而你以为它是结构体时,选这个方法。两种常见情况:

  • 像 fn find_order() -> u32 这样的函数只返回订单的 ID,于是 order.total 报错,因为 order 是一个 u32。
  • HashMap<&str, u32> 只存了分数,于是 scores["ana"].score 报错,因为每个值都是 u32,而不是 Player。

先想清楚你真正需要什么。如果需要好几项数据,就把返回类型或值的类型改成结构体:

use std::collections::HashMap;
struct Order {
id: u32,
total: u32, // 单位是分
}
// 返回整个 Order,而不只是一个数字
fn new_order(id: u32, total: u32) -> Order {
Order { id, total }
}
struct Player {
score: u32,
level: u8,
}
fn main() {
let order = new_order(7, 1999);
println!("订单 {} 的金额是 {} 分", order.id, order.total);
// map 里存的是完整的 Player,而不只是分数
let mut players: HashMap<&str, Player> = HashMap::new();
players.insert("ana", Player { score: 10, level: 2 });
let ana = &players["ana"];
println!("ana:分数 {},等级 {}", ana.score, ana.level);
}

输出:

订单 7 的金额是 1999 分
ana:分数 10,等级 2
▶ 动手试一试

如果你只需要一个数字,那就保留 u32,把 .field 那部分去掉就好。参见结构体和哈希 map。

当错误指向 .0 或 .1 时,选这个方法。元组和元组结构体用位置来表示字段,很容易多往里走一层。写完 let x = point.0; 之后,x 已经是数字了,所以 x.0 会报 `{integer}` is a primitive type。像 struct Meters(u32); 这样的包装类型也一样:一个 .0 取出里面的 u32,再来一个 .0 就会报错。

struct Meters(u32);
fn main() {
let point = (3, 4);
let x = point.0; // x 已经是数字 3 了
println!("x = {}", x); // 不是 x.0
let m = Meters(42);
println!("{} 米", m.0); // 一个 .0 就能从元组结构体里取出 u32
let line = ((0, 0), (3, 4)); // 嵌套元组:每一层用一个位置
println!("终点 y = {}", line.1.1);
}

输出:

x = 3
42 米
终点 y = 4
▶ 动手试一试

数一数层数:每个 .0 或 .1 都往元组里走一层,碰到数字就停下。如果位置搞得你头晕,就改用结构体,给它们起名字。参见按位置访问元素和什么时候用元组,什么时候用结构体。

方法忘了写 ()。 在 f64 上写 price.round,或在 i32 上写 x.abs,报的不是 E0610,而是 E0615(“attempted to take value of method”,把方法当成值来取),因为这些名字确实是方法。修复:price.round()、x.abs()。有一点出人意料:如果类型还是 {integer}(比如 let x = -5;),连 x.abs 都会报 E0610,而改成 x.abs() 之后又会报 E0689:“can’t call method abs on ambiguous numeric type {integer}”(不能在类型不明确的数字上调用方法)。给它一个类型,let x: i32 = -5;,x.abs() 就能用了。

数字的引用。 如果变量是 &u32 而不是 u32,错误代码就变成了 E0609:no field `value` on type `&u32`。常见的地方是 for (name, score) in &players 这样的循环,这里 score 是 &i32,于是 score.1 会报 no field `1` on type `&{integer}`。原因一样,修法也一样:score 本身就是那个数字。

len() 或 count() 的结果。 let len = scores.len(); 得到的是一个 usize,所以 len.count 会报 `usize` is a primitive type。修复:直接用 len。

带单位的数字。 如果你想写 price.cents 或 distance.meters,就定义一个小结构体:struct Price { cents: u64 }。这样 price.cents 就能用了,而且类型系统还会防止你把价格和别的数字搞混。参见结构体。

小测验

1. 写完 let total = cart.total;(total 是一个 u32 字段)之后,total.amount 会怎样?

2. 错误信息里的 {integer} 是什么意思?

3. let x = point.0; 之后,x.0 报了 E0610。怎么修?

得分: 0 / 3