Bỏ qua để đến nội dung

Lỗi Rust E0658: use of unstable library feature (tính năng chỉ có trên nightly)

E0658 nghĩa là: bạn đã dùng một thứ có tồn tại trong Rust, nhưng chưa hoàn thiện, nên Rust stable không cho bạn dùng. Thường đó là một phương thức bạn tìm thấy trong tài liệu thư viện chuẩn hoặc trong một bài blog, như .intersperse() hay .try_collect(), hoặc một cú pháp như try { ... }. Code của bạn không sai, và máy của bạn cũng chẳng hỏng gì. Bạn chỉ vừa với tới một tính năng chỉ chạy trên trình biên dịch nightly. Gần như lần nào cũng có một cách stable để làm điều tương tự trong một dòng. (Trang này thuộc loạt bài Giải thích các lỗi trình biên dịch 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-only method
| ^^^^^^^^^^^
|
= note: see issue #79524 <https://github.com/rust-lang/rust/issues/79524> for more information

Thông báo của trình biên dịch luôn bằng tiếng Anh: “use of unstable library feature iter_intersperse” nghĩa là “dùng tính năng chưa ổn định iter_intersperse của thư viện”. Đây là chương trình gây ra lỗi đó (chú thích được giữ bằng tiếng Anh vì nó xuất hiện nguyên văn trong thông báo lỗi):

fn main() {
let tags = ["rust", "web", "cli"];
let joined: String = tags.iter().copied().intersperse(", ").collect(); // ❌ nightly-only method
println!("{}", joined);
}

Dấu ^^^^^^^^^^^ chỉ vào intersperse ở dòng 3. Dòng tiêu đề cho biết tên tính năng trong cặp dấu backtick, iter_intersperse. Đó là cái tên mà nhóm phát triển Rust dùng cho phần thư viện chuẩn chưa hoàn thiện này. Dòng note: dẫn tới issue trên GitHub nơi tính năng đang được thảo luận.

Vậy là rustc đã tìm thấy phương thức. Nó biết chính xác ý bạn. Nó chỉ không chịu biên dịch, vì intersperse vẫn được đánh dấu là unstable (chưa ổn định): nó có thể đổi tên, đổi đối số hoặc đổi hành vi trước khi hoàn thiện, hoặc có thể bị bỏ đi.

Những dạng tiêu đề khác mang cùng ý nghĩa, nhưng cho tính năng của ngôn ngữ thay vì của thư viện: the `!` type is experimental, `try` expression is experimental, hoặc associated type defaults are unstable.

Giống như một cửa hàng có món hàng mới trên kệ, dán nhãn “chỉ dành cho nhân viên, đang thử nghiệm”. Bạn nhìn thấy nó, đọc được nhãn, nhưng quầy thu ngân chưa chịu bán cho bạn.

Rust đưa ra một lời hứa mạnh mẽ: code biên dịch được trên Rust stable hôm nay sẽ tiếp tục biên dịch được trên mọi bản Rust 1.x stable sau này. Để giữ lời hứa đó, tính năng mới trước hết xuất hiện trên kênh nightly, nơi mọi người có thể dùng thử và thiết kế vẫn còn thay đổi được. Chỉ khi một tính năng đã xong thì nó mới trở thành stable, thường sau nhiều tháng hoặc nhiều năm thử nghiệm.

Rust stable từ chối biên dịch mọi tính năng chưa ổn định, nên bạn không thể xây dựng trên một thứ có thể thay đổi dưới chân mình. Nếu bạn thật sự muốn thử một tính năng, nightly cho phép bạn bật nó theo tên bằng #![feature(iter_intersperse)]. Trên stable, chính dòng đó cũng là lỗi (E0554).

Tài liệu thư viện chuẩn cho thấy cả hai loại cạnh nhau. Mục stable có số phiên bản ở bên phải, như 1.82.0: phiên bản Rust mà nó trở thành stable. Mục chưa ổn định có một khung ghi “🔬 This is a nightly-only experimental API.” (đây là API thử nghiệm chỉ có trên nightly). Khung đó là cách nhanh nhất để nhận ra một E0658 trước khi bạn viết dòng code nào. Xem Công cụ và thiết lập Rust để biết các kênh phát hành của Rust và rustup liên quan với nhau thế nào.

Gần như lần nào cũng nên chọn cách này. Các phương thức chưa ổn định thường là lối tắt cho một việc mà Rust stable đã làm được. Để nối các chuỗi bằng một dấu phân cách, join trên slice hoặc Vec làm đúng việc mà intersperse(...).collect() định làm:

fn main() {
let tags = ["rust", "web", "cli"];
let joined = tags.join(", ");
println!("{}", joined);
// Chuỗi do iterator tạo ra: collect vào một Vec trước, rồi join.
let upper: Vec<String> = tags.iter().map(|t| t.to_uppercase()).collect();
println!("{}", upper.join(" | "));
}

Kết quả:

rust, web, cli
RUST | WEB | CLI
▶ Tự chạy thử

Các thẻ được giữ bằng tiếng Anh vì chúng được chuyển sang chữ hoa bằng to_uppercase(). Nếu bạn muốn giữ mọi thứ trong một chuỗi iterator duy nhất, crate phổ biến itertools có join và intersperse stable cho iterator. Xem Chuỗi và Iterator.

Cách sửa 2: Kiểm tra tài liệu xem có nhãn “nightly-only” không

Phần tiêu đề “Cách sửa 2: Kiểm tra tài liệu xem có nhãn “nightly-only” không”

Chọn cách này khi bạn chép một phương thức từ tài liệu, từ câu trả lời của AI hoặc từ một bài blog. Mở phương thức đó trong tài liệu std: nếu nó có khung 🔬, hãy tìm thêm một chút để thấy cách stable. Một trường hợp hay gặp là .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-only
| ^^^^^^^^^^^
|
= note: see issue #94047 <https://github.com/rust-lang/rust/issues/94047> for more information

Chương trình bị lỗi:

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

Rust stable đã làm được việc này bằng collect() thông thường. Khi các phần tử là Result và bạn collect vào một Result<Vec<_>, _>, bạn nhận được Ok chứa mọi giá trị, hoặc Err đầu tiên:

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"]));
}

Kết quả:

Ok([3, 14, 15])
Err(ParseIntError { kind: InvalidDigit })
▶ Tự chạy thử

Vài phương thức chưa ổn định khác bạn có thể gặp, kèm con đường stable: .array_chunks() trên iterator (iter_array_chunks; hãy dùng .as_chunks() hoặc .chunks() trên slice), và .map_windows() (dùng .windows() trên slice). Xem Result và Slice.

Cách sửa 3: Cập nhật Rust nếu tính năng vừa được ổn định gần đây

Phần tiêu đề “Cách sửa 3: Cập nhật Rust nếu tính năng vừa được ổn định gần đây”

Chọn cách này khi tài liệu hiện số phiên bản thay vì khung 🔬, hoặc cùng đoạn code đó biên dịch được trên máy bạn bè hay trên Playground. Khi đó tính năng đã stable, chỉ là mới hơn trình biên dịch của bạn. Ví dụ, as_chunks trên slice trở thành stable từ Rust 1.88. Trên một bản Rust cũ hơn bạn sẽ gặp E0658 với nó; trên bản hiện tại thì nó cứ thế chạy:

fn main() {
let readings = [21, 22, 19, 20, 23];
let (pairs, rest) = readings.as_chunks::<2>();
for [a, b] in pairs {
println!("{} và {}", a, b);
}
println!("còn dư: {:?}", rest);
}

Kết quả:

21 và 22
19 và 20
còn dư: [23]
▶ Tự chạy thử

Kiểm tra phiên bản và cập nhật:

Terminal window
rustc --version
rustup update

Nếu sau đó rustc --version vẫn hiện phiên bản cũ, hãy tìm file rust-toolchain.toml trong dự án. Nó ghim dự án vào một phiên bản Rust cố định, và rustup show cho bạn biết phiên bản nào đang được dùng. Xem Công cụ và thiết lập Rust.

Cách sửa 4: Chủ động dùng nightly với #![feature], chỉ khi bạn thật sự muốn

Phần tiêu đề “Cách sửa 4: Chủ động dùng nightly với #![feature], chỉ khi bạn thật sự muốn”

Hãy chọn cách này sau cùng, và chỉ khi không có cách thay thế stable nào và bạn chấp nhận việc code có thể hỏng trên một bản nightly sau này. Bạn cần hai thứ: một dòng #![feature(...)] ở đầu main.rs (hoặc lib.rs) với tên tính năng lấy từ thông báo lỗi, và trình biên dịch nightly. Trên stable, chính dòng #![feature] bị từ chối:

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

Chương trình, chỉ build được trên nightly:

#![feature(iter_intersperse)] // ❌ only allowed on nightly Rust
fn main() {
let tags = ["rust", "web", "cli"];
let joined: String = tags.iter().copied().intersperse(", ").collect();
println!("{}", joined);
}

Cài nightly bên cạnh Rust stable của bạn và chỉ dùng nó cho dự án này hoặc cho lệnh này:

Terminal window
rustup toolchain install nightly
cargo +nightly run

Hãy thành thật với bản thân ở đây. Để học, viết test hay làm công cụ nhỏ, nightly hoàn toàn ổn. Còn với một thư viện người khác phụ thuộc vào, hay bất cứ thứ gì bạn phải bảo trì, hãy ở lại với stable: phần lớn các crate bạn dùng cũng vậy.

Vec<i32, i32> khi ý bạn là một danh sách các cặp. Tham số thứ hai của Vec là bộ cấp phát bộ nhớ (allocator), thứ chưa ổn định, nên lỗi đầu tiên là về allocator_api (theo sau còn vài lỗi khác):

error[E0658]: use of unstable library feature `allocator_api`
--> src/main.rs:2:26
|
2 | let points: Vec<i32, i32> = vec![]; // ❌ meant a list of (x, y) pairs
| -----^^^
| |
| help: consider wrapping the inner types in tuple: `(i32, i32)`
fn main() {
let points: Vec<i32, i32> = vec![]; // ❌ meant a list of (x, y) pairs
println!("{} points", points.len());
}

Cách sửa: làm theo help, Vec<(i32, i32)>. Xem Vector và Tuple. Cùng sai sót đó trên Option hoặc Result là E0107.

Kiểu ! trong chữ ký của bạn. ! nghĩa là “không bao giờ xảy ra”. Nó dùng được làm kiểu trả về của một hàm không bao giờ trả về, nhưng ở mọi chỗ khác thì vẫn chưa ổn định:

error[E0658]: the `!` type is experimental
--> src/main.rs:1:34
|
1 | fn default_port() -> Result<u16, !> { // ❌ the `!` type is nightly-only here
| ^
fn default_port() -> Result<u16, !> { // ❌ the `!` type is nightly-only here
Ok(8080)
}
fn main() {
println!("{:?}", default_port());
}

Cách sửa: dùng std::convert::Infallible, kiểu stable mang nghĩa “lỗi này không thể xảy ra”, hoặc chỉ cần trả về u16 nếu chẳng có gì có thể thất bại.

Khối try. Một khối try { ... } cho phép bạn dùng ? mà không cần viết hàm, nhưng nó chỉ có trên nightly (`try` expression is experimental). Cách sửa: chuyển code vào một hàm nhỏ trả về Result và dùng ? ở đó, như ở cách sửa 2. Xem Result.

Có liên quan, nhưng không phải E0658: code viết cho edition 2024, như if let Some(a) = age && a >= 18, sẽ cho ra let chains are only allowed in Rust 2024 or later trên edition cũ hơn. Cách sửa: đặt edition = "2024" trong Cargo.toml, hoặc lồng hai câu if vào nhau.

  • E0554: #![feature] may not be used on the stable release channel, lỗi kéo theo khi bạn thử cách sửa 4 trên stable.
  • E0599: không tìm thấy phương thức; khi phương thức hoàn toàn không tồn tại (hoặc đến từ một crate bạn chưa thêm vào), bạn nhận lỗi này thay thế.
  • E0107: sai số lượng đối số generic, như Option<i32, i32> (họ hàng của trường hợp Vec<i32, i32>).
  • E0277: ràng buộc trait không được thỏa mãn; một trong các lỗi theo sau Vec<i32, i32>.
  • E0463: không tìm thấy crate, nếu bạn chuyển sang itertools mà quên thêm nó vào Cargo.toml.
  • Tất cả lỗi trình biên dịch: danh sách đầy đủ các trang lỗi.

Kiểm tra nhanh

1. Trang tài liệu std của một phương thức có khung ghi “This is a nightly-only experimental API”. Chuyện gì xảy ra nếu bạn gọi nó trên Rust stable?

2. .try_collect() gây ra E0658. Cách stable thông thường để biến một iterator gồm các Result thành Result<Vec<_>, _> là gì?

3. Code biên dịch được trên Playground lại cho bạn E0658 với một phương thức mà tài liệu ghi 1.88.0. Bạn nên thử gì trước?

Điểm: 0 / 3