跳转到内容

Rust 错误 E0614:type cannot be dereferenced(类型不能解引用)

E0614 的意思是:你在一个不是引用的东西前面写了 *,所以没有什么可以“顺着找过去”的。 * 运算符的意思是“到这个引用指向的值那里去”。一个普通的 u32、i32 或结构体并没有指向任何地方:它本身就是那个值。这个错误通常出现在闭包和循环里,因为在那里很容易搞不清手上拿的是 &u32 还是 u32。rustc 会在第一行告诉你真正的类型,而修复几乎总是删掉一个 *。这个系列的其他错误,请看 Rust 编译错误详解。

error[E0614]: type `u32` cannot be dereferenced
--> src/main.rs:3:64
|
3 | let doubled: Vec<u32> = visits.iter().copied().map(|count| *count * 2).collect(); // ❌ `count` 已经是 `u32` 了
| ^^^^^^ can't be dereferenced

第一行的意思是“类型 u32 不能解引用”。

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

fn main() {
let visits: Vec<u32> = vec![12, 0, 7, 30];
let doubled: Vec<u32> = visits.iter().copied().map(|count| *count * 2).collect(); // ❌ `count` 已经是 `u32` 了
println!("{:?}", doubled);
}

^^^^^^ 指向第 3 行的 *count,标注是“can’t be dereferenced”(不能解引用)。第一行告诉你 count 到底是什么:一个 u32,而不是 &u32。

顺着这条链看一看类型:

  1. visits.iter() 给你的是元素的引用:&u32、&u32……
  2. .copied() 把每个 &u32 变成一份普通的 u32 副本。引用就没有了。
  3. 所以在 .map(|count| ...) 里,count 是 u32,而 *count 是在让 Rust 顺着一个根本不存在的指针找过去。

如果第一行写的是 type `{integer}` cannot be dereferenced,也是同一回事。{integer} 是 rustc 表示“某种还没确定下来的整数类型”的写法,通常是像 72 这样你从没指定类型的数字。

就像你问别人“这个地址上有什么?”,递过去的却是包裹本身,而不是一张写着地址的纸条。东西已经在他手上了,没有地址可以去。

在 Rust 里,引用(&T)和值本身(T)是不同的类型。* 只能用在指向别处的东西上:引用 &T 和 &mut T,Box<T>、Rc<T> 这样的智能指针,以及任何实现了 Deref trait 的类型。u32 或普通的结构体都不是这些,所以对它们用 * 没有意义。

Rust 本可以悄悄忽略多出来的 *,但它没有这么做。多写一个 *,通常说明你搞错了自己手上的类型,而这种混乱往往会引出别的 bug:比较了错误的东西,或者把本想借用的值移动走了。所以 rustc 会停下来,告诉你真正的类型。参见借用和智能指针。

相反的错误有自己的错误代码。如果你在引用上忘了写 *,比如 count 是 &u32 时写了 count > 10,得到的是 E0308 mismatched types(类型不匹配)。

几乎每次都选这个,尤其是在 .copied()、.cloned()、对 Vec 调用 .into_iter(),或者其他直接交给你值的方法之后。值已经在你手上了,直接用就行:

fn main() {
let visits: Vec<u32> = vec![12, 0, 7, 30];
let doubled: Vec<u32> = visits.iter().copied().map(|count| count * 2).collect();
println!("{:?}", doubled);
// `filter` 把每个元素借给闭包,所以这里的 `count` 确实是 `&u32`,
// 写 `*` 是对的。
let busy: Vec<u32> = visits.iter().copied().filter(|count| *count > 10).collect();
println!("{:?}", busy);
}

输出:

[24, 0, 14, 60]
[12, 30]
▶ 动手试一试

第二部分会难住很多人:在同一条链里,map 拿到的是元素本身,而 filter 拿到的是元素的引用。拿不准的时候,就去文档里看看这个方法。filter 接收的是 FnMut(&Self::Item) -> bool,里面有个 &。参见迭代器和闭包。

修复 2:在 for 循环里,想清楚要值还是要引用

Section titled “修复 2:在 for 循环里,想清楚要值还是要引用”

当 * 出现在 for 循环里时,选这个。for score in scores 会把 Vec 拆开,交给你每一个值,所以 *score 会失败:

error[E0614]: type `{integer}` cannot be dereferenced
--> src/main.rs:5:18
|
5 | total += *score; // ❌ `score` 是数字,不是引用
| ^^^^^^ can't be dereferenced

出错的程序:

fn main() {
let scores = vec![72, 95, 88];
let mut total = 0;
for score in scores {
total += *score; // ❌ `score` 是数字,不是引用
}
println!("总分:{}", total);
}

要么去掉 *,要么遍历 &scores。这样每个 score 都是 &i32,* 就是对的,而且循环结束后你还能继续用这个 Vec:

fn main() {
let scores = vec![72, 95, 88];
let mut total = 0;
for score in &scores {
total += *score; // 现在 `score` 是 `&i32`
}
println!("总分:{}", total);
// `scores` 没有被移动进循环,所以它还在。
println!("共 {} 个分数,平均 {} 分", scores.len(), total / scores.len() as i32);
}

输出:

总分:255
共 3 个分数,平均 85 分
▶ 动手试一试

一个简单的规则:for x in v 给你值,for x in &v 给你 & 引用,for x in &mut v 给你 &mut 引用。只有后两种需要 *。参见 for 循环和 vector。

修复 3:用 &x 匹配引用,或者用 *x,但不要两个都用

Section titled “修复 3:用 &x 匹配引用,或者用 *x,但不要两个都用”

当闭包或循环的模式里已经有一个 & 时,比如 |&p|,选这个。模式里的 & 会替你把引用拆开,所以 p 已经是普通的数字,*p 就多走了一步:

error[E0614]: type `{integer}` cannot be dereferenced
--> src/main.rs:3:53
|
3 | let with_fee: Vec<i32> = prices.iter().map(|&p| *p + 1).collect(); // ❌ `&p` 已经去掉了 `&`
| ^^ can't be dereferenced

出错的程序:

fn main() {
let prices = [3, 5, 8];
let with_fee: Vec<i32> = prices.iter().map(|&p| *p + 1).collect(); // ❌ `&p` 已经去掉了 `&`
println!("{:?}", with_fee);
}

去掉引用的方法只选一种。下面两行做的是同一件事:

fn main() {
let prices = [3, 5, 8];
let with_fee: Vec<i32> = prices.iter().map(|&p| p + 1).collect(); // 由模式去掉 `&`
let same: Vec<i32> = prices.iter().map(|p| *p + 1).collect(); // 由 `*` 去掉 `&`
println!("{:?} {:?}", with_fee, same);
// 在 `.iter()` 上调用 `filter`,拿到的是 `&&i32`:两个 `&`,所以模式写成 `&&p`。
let pricey = prices.iter().filter(|&&p| p > 4).count();
println!("有 {} 件商品比较贵", pricey);
}

输出:

[4, 6, 9] [4, 6, 9]
有 2 件商品比较贵
▶ 动手试一试

循环也是一样:for &(name, qty) in &orders 会拆开每个引用,所以直接用 name 和 qty,不要加 *。参见模式匹配。

修复 4:确认类型,包装类型就用字段或 Deref

Section titled “修复 4:确认类型,包装类型就用字段或 Deref”

当第一行写的是你自己的类型时,比如 type `Meters` cannot be dereferenced,选这个。结构体也不是引用,哪怕它只包着一个值:

error[E0614]: type `Meters` cannot be dereferenced
--> src/main.rs:5:18
|
5 | let longer = *run + 1.0; // ❌ `run` 是 `Meters`,不是引用
| ^^^^ can't be dereferenced

出错的程序:

struct Meters(f64);
fn main() {
let run = Meters(2.5);
let longer = *run + 1.0; // ❌ `run` 是 `Meters`,不是引用
println!("{} m", longer);
}

要取出元组结构体里的数字,就用它的字段 .0:

struct Meters(f64);
fn main() {
let run = Meters(2.5);
let longer = run.0 + 1.0;
println!("{} m", longer);
}

输出:

3.5 m
▶ 动手试一试

如果你的类型真的是一个类似智能指针的包装类型,而你希望 * 能用在它上面,就为它实现 std::ops::Deref。只有包装类型才应该这样做;对于 Meters 这样的“newtype”,用 .0 或一个方法会更清楚。不确定某个变量是什么类型?在编辑器里把鼠标悬停在它上面(rust-analyzer 会显示类型),或者直接相信第一行:它总是写出真正的类型。参见结构体和智能指针。

在结构体字段前加 *。 config 是一个结构体(或指向结构体的引用),retries 是 u32,这时写 *config.retries 会得到 type `u32` cannot be dereferenced。. 已经替你顺着引用找过去了。修复:写成 config.retries。

按值遍历 HashMap。 for (word, count) in counts { total += *count; } 会得到 type `u32` cannot be dereferenced,因为直接遍历 counts 拿到的是有所有权的键和值。修复:去掉 *,或者如果之后还要用这个 map,就遍历 &counts。参见哈希 map。

在 .copied().max() 或 .cloned().find(...) 之后。 它们给出的是 Option<u32>,而不是 Option<&u32>,所以在 if let Some(best) = ... { *best } 里,* 是多余的。修复:直接用 best。如果没有 .copied(),.iter().max() 给出的是 Option<&u32>,这时写 *best 就是对的。

  • E0308:mismatched types(类型不匹配)。相反的错误:引用碰上普通的值时少了一个 *。
  • E0369:二元运算不能用在某个类型上。引用和值混在一起时,也可能出现这个错误。
  • E0507:cannot move out of a reference(不能从引用中移出值)。这里 * 确实能用,但它会移动一个不是 Copy 的值。
  • E0610:{integer} is a primitive type and has no fields(基本类型没有字段),这个错误“它只是个数字”的近亲。
  • E0599:no method found(找不到方法)。值的类型和你以为的不一样时会出现。
  • 所有编译错误:错误页面的完整列表。

小测验

1. 在 visits.iter().copied().map(|count| *count + 1) 里,为什么 *count 会报 E0614?

2. scores 是 Vec<i32>,代码是 for score in &scores { total += *score; }。能编译吗?

3. prices.iter().map(|&p| *p * 2) 报了 E0614。哪种写法是对的?

得分: 0 / 3