跳转到内容

Rust 错误 E0658:use of unstable library feature(使用了不稳定的特性)

E0658 的意思是:你用到的东西在 Rust 里确实存在,但还没有完成,所以稳定版(stable)Rust 不让你用。 它通常是你在标准库文档或博客里看到的某个方法,比如 .intersperse() 或 .try_collect(),也可能是 try { ... } 这样的语法。你的代码没有写错,你电脑上的环境也没有坏。你只是用到了一个只能在 nightly 编译器上用的特性。几乎每一次,都有一种稳定的写法,一行就能做到同样的事。这个系列的其他错误,请看 Rust 编译错误详解。

error[E0658]: use of unstable library feature `iter_intersperse`
--> src/main.rs:3:47
|
3 | let joined: String = tags.iter().copied().intersperse(", ").collect(); // ❌ 只有 nightly 才有的方法
| ^^^^^^^^^^^
|
= note: see issue #79524 <https://github.com/rust-lang/rust/issues/79524> for more information

第一行的意思是“使用了不稳定的库特性 iter_intersperse”。note: 给出了讨论这个特性的 GitHub issue 链接。

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

fn main() {
let tags = ["rust", "web", "cli"];
let joined: String = tags.iter().copied().intersperse(", ").collect(); // ❌ 只有 nightly 才有的方法
println!("{}", joined);
}

^^^^^^^^^^^ 指向第 3 行的 intersperse。第一行用反引号给出了特性名:iter_intersperse。这是 Rust 团队给标准库里这个未完成部分起的名字。note: 链接到正在讨论这个特性的 GitHub issue。

也就是说,rustc 找到了这个方法,也完全明白你想做什么。它只是不肯编译,因为 intersperse 仍然被标记为不稳定(unstable):在完成之前,它的名字、参数或行为都可能改变,甚至可能被删掉。

对于语言特性(而不是库特性),第一行会换一种说法,意思相同:the `!` type is experimental(! 类型还是实验性的)、`try` expression is experimental(try 表达式还是实验性的),或者 associated type defaults are unstable(关联类型默认值还不稳定)。

就像商店的货架上摆着一件新商品,贴着“仅限员工,仍在测试”的标签。你看得见,也能读标签上的说明,但收银台暂时不卖给你。

Rust 有一个很重要的承诺:今天能在稳定版 Rust 上编译的代码,在以后每一个 1.x 稳定版上也都能编译。为了守住这个承诺,新特性会先进入 nightly 渠道,大家可以在那里试用,设计也还能修改。只有等一个特性真正完成,它才会变成稳定的,这通常要经过几个月甚至几年的测试。

稳定版 Rust 根本不会编译不稳定的特性,这样你就不会把代码建立在随时可能变化的东西上。如果你真的想试一试,nightly 允许你用 #![feature(iter_intersperse)] 按名字打开它。在稳定版上,连这一行本身都是错误(E0554)。

标准库文档会把两种东西并排列出来。稳定的条目右侧有一个版本号,比如 1.82.0,表示它在哪个 Rust 版本变成稳定的。不稳定的条目则有一个框,写着 “🔬 This is a nightly-only experimental API.”(这是只有 nightly 才有的实验性 API)。在动手写代码之前,这个框是发现 E0658 最快的方法。Rust 的各个发布渠道和 rustup 是怎么配合的,请看 Rust 工具与环境配置。

几乎每次都选这个。不稳定的方法通常只是一个捷径,稳定版 Rust 早就能做到同样的事。要用分隔符把字符串拼起来,在 slice 或 Vec 上调用 join,效果和 intersperse(...).collect() 一样:

fn main() {
let tags = ["rust", "web", "cli"];
let joined = tags.join(", ");
println!("{}", joined);
// 由迭代器生成的字符串:先收集到 Vec 里,再 join。
let upper: Vec<String> = tags.iter().map(|t| t.to_uppercase()).collect();
println!("{}", upper.join(" | "));
}

输出:

rust, web, cli
RUST | WEB | CLI
▶ 动手试一试

如果你更想把一切都写在一条迭代器链里,常用的 itertools crate 为迭代器提供了稳定可用的 join 和 intersperse。参见字符串和迭代器。

修复 2:在文档里找“nightly-only”标记

Section titled “修复 2:在文档里找“nightly-only”标记”

当你是从文档、AI 的回答或博客里复制来的方法时,选这个。在标准库文档里打开这个方法:如果它带着 🔬 框,就再往下找找稳定的写法。一个常见的例子是 .try_collect():

error[E0658]: use of unstable library feature `iterator_try_collect`
--> src/main.rs:3:80
|
3 | let numbers: Result<Vec<i32>, _> = inputs.iter().map(|s| s.parse::<i32>()).try_collect(); // ❌ 只有 nightly 才有
| ^^^^^^^^^^^
|
= note: see issue #94047 <https://github.com/rust-lang/rust/issues/94047> for more information

出错的程序:

fn main() {
let inputs = ["3", "14", "x"];
let numbers: Result<Vec<i32>, _> = inputs.iter().map(|s| s.parse::<i32>()).try_collect(); // ❌ 只有 nightly 才有
println!("{:?}", numbers);
}

稳定版 Rust 用普通的 collect() 就能做到。当元素是 Result,而你把它们收集成 Result<Vec<_>, _> 时,要么得到装着所有值的 Ok,要么得到第一个 Err:

use std::num::ParseIntError;
fn parse_all(inputs: &[&str]) -> Result<Vec<i32>, ParseIntError> {
inputs.iter().map(|s| s.parse::<i32>()).collect()
}
fn main() {
println!("{:?}", parse_all(&["3", "14", "15"]));
println!("{:?}", parse_all(&["3", "x", "15"]));
}

输出:

Ok([3, 14, 15])
Err(ParseIntError { kind: InvalidDigit })
▶ 动手试一试

你还可能碰到其他不稳定的方法,下面是它们的稳定替代写法:迭代器上的 .array_chunks()(iter_array_chunks;改用 slice 上的 .as_chunks() 或 .chunks()),以及 .map_windows()(改用 slice 上的 .windows())。参见 Result 和 slice。

修复 3:如果特性是最近才稳定的,就升级 Rust

Section titled “修复 3:如果特性是最近才稳定的,就升级 Rust”

当文档显示的是版本号而不是 🔬 框,或者同样的代码在朋友的电脑上或 Playground 里能编译时,选这个。这说明这个特性已经稳定了,只是比你的编译器更新。比如,slice 上的 as_chunks 在 Rust 1.88 变成了稳定的。在更老的 Rust 上用它会得到 E0658;在现在的版本上,它直接就能用:

fn main() {
let readings = [21, 22, 19, 20, 23];
let (pairs, rest) = readings.as_chunks::<2>();
for [a, b] in pairs {
println!("{} 和 {}", a, b);
}
println!("剩下的:{:?}", rest);
}

输出:

21 和 22
19 和 20
剩下的:[23]
▶ 动手试一试

查看版本并升级:

Terminal window
rustc --version
rustup update

如果之后 rustc --version 显示的仍然是旧版本,就看看项目里有没有 rust-toolchain.toml 文件。它会把项目固定在某一个 Rust 版本上,rustup show 会告诉你当前用的是哪一个。参见 Rust 工具与环境配置。

修复 4:用 #![feature] 切换到 nightly,前提是你真的想这样做

Section titled “修复 4:用 #![feature] 切换到 nightly,前提是你真的想这样做”

最后才选这个,而且只在没有稳定替代写法、你也能接受代码在将来某个 nightly 上失效的时候。你需要两样东西:在 main.rs(或 lib.rs)的最上面写一行 #![feature(...)],里面填上错误信息里的特性名;以及 nightly 编译器。在稳定版上,#![feature] 这一行本身就会被拒绝:

error[E0554]: `#![feature]` may not be used on the stable release channel
--> src/main.rs:1:1
|
1 | #![feature(iter_intersperse)] // ❌ 只能在 nightly Rust 上使用
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

这个程序只能在 nightly 上构建:

#![feature(iter_intersperse)] // ❌ 只能在 nightly Rust 上使用
fn main() {
let tags = ["rust", "web", "cli"];
let joined: String = tags.iter().copied().intersperse(", ").collect();
println!("{}", joined);
}

在稳定版 Rust 旁边再装一个 nightly,只在这个项目或这条命令里用它:

Terminal window
rustup toolchain install nightly
cargo +nightly run

这里要对自己诚实。用来学习、写测试或做小工具,nightly 完全没问题。但如果是别人要依赖的库,或者任何需要你长期维护的东西,就留在稳定版上:你用的大多数 crate 也是这么做的。

本想写一个坐标对的列表,却写成了 Vec<i32, i32>。 Vec 的第二个参数是内存分配器(allocator),而它是不稳定的,所以第一个错误是关于 allocator_api 的(后面还会跟着几个错误):

error[E0658]: use of unstable library feature `allocator_api`
--> src/main.rs:2:26
|
2 | let points: Vec<i32, i32> = vec![]; // ❌ 本来想要 (x, y) 坐标对的列表
| -----^^^
| |
| help: consider wrapping the inner types in tuple: `(i32, i32)`
fn main() {
let points: Vec<i32, i32> = vec![]; // ❌ 本来想要 (x, y) 坐标对的列表
println!("{} points", points.len());
}

修复:照 help 说的做,写成 Vec<(i32, i32)>。参见 vector 和元组。在 Option 或 Result 上犯同样的错误,得到的是 E0107。

在自己的函数签名里用 ! 类型。 ! 的意思是“永远不会发生”。它可以作为永不返回的函数的返回类型,但用在其他任何地方都还是不稳定的:

error[E0658]: the `!` type is experimental
--> src/main.rs:1:34
|
1 | fn default_port() -> Result<u16, !> { // ❌ 在这里 `!` 类型只有 nightly 才能用
| ^
fn default_port() -> Result<u16, !> { // ❌ 在这里 `!` 类型只有 nightly 才能用
Ok(8080)
}
fn main() {
println!("{:?}", default_port());
}

修复:改用 std::convert::Infallible,它是稳定版里表示“这个错误不可能发生”的类型;如果根本不会失败,直接返回 u16 也行。

try 块。 try { ... } 块让你不用写函数就能使用 ?,但它只有 nightly 才有(`try` expression is experimental)。修复:把代码挪进一个返回 Result 的小函数,在那里用 ?,就像修复 2 那样。参见 Result。

相关但不是 E0658 的情况:为 2024 版次(edition)写的代码,比如 if let Some(a) = age && a >= 18,在更老的版次上会报 let chains are only allowed in Rust 2024 or later(let 链只能在 Rust 2024 或更新的版次中使用)。修复:在 Cargo.toml 里设置 edition = "2024",或者把两个 if 嵌套起来写。

  • E0554:#![feature] may not be used on the stable release channel。在稳定版上尝试修复 4 时,接着就会出现这个错误。
  • E0599:no method found(找不到方法)。如果这个方法根本不存在(或者来自你还没添加的 crate),你会得到这个错误。
  • E0107:泛型参数的数量不对,比如 Option<i32, i32>(Vec<i32, i32> 那种情况的近亲)。
  • E0277:trait 约束没有被满足;Vec<i32, i32> 后面跟着出现的错误之一。
  • E0463:can’t find crate(找不到 crate)。如果你改用 itertools,却忘了把它加进 Cargo.toml,就会看到它。
  • 所有编译错误:错误页面的完整列表。

小测验

1. 标准库文档里某个方法的页面上有一个框,写着“This is a nightly-only experimental API”。在稳定版 Rust 上调用它会怎样?

2. .try_collect() 报了 E0658。把一个产生 Result 的迭代器变成 Result<Vec<_>, _>,通常的稳定写法是什么?

3. 一段代码在 Playground 上能编译,在你这里却因为一个方法报 E0658,而文档上这个方法标着 1.88.0。你应该先试什么?

得分: 0 / 3