Lỗi Rust E0435: attempt to use a non-constant value in a constant (không phải hằng)
E0435 nghĩa là: một thứ phải được biết ngay trong lúc chương trình đang được biên dịch lại cố dùng một giá trị chỉ tồn tại khi chương trình chạy. Chỗ hay gặp nhất là độ dài mảng, như [0.0; days], hoặc một const đọc một biến let. Đoạn code trông hoàn toàn hợp lý, và ở nhiều ngôn ngữ khác nó sẽ chạy được. Trong Rust, hằng số và độ dài mảng do trình biên dịch tính ra, trước khi chương trình bắt đầu chạy, nên chúng không thể nhìn vào các biến thông thường. Đây là lỗi rất phổ biến, và cách sửa thường chỉ là một từ. (Trang này thuộc loạt bài Giải thích các lỗi trình biên dịch Rust.)
Thông báo lỗi
Phần tiêu đề “Thông báo lỗi”error[E0435]: attempt to use a non-constant value in a constant --> src/main.rs:3:26 |3 | let rainfall = [0.0; days]; // ❌ an array's length must be known at compile time | ^^^^ non-constant value |help: consider using `const` instead of `let` |2 - let days = 7;2 + const days: /* Type */ = 7; |Thông báo của trình biên dịch luôn bằng tiếng Anh: “attempt to use a non-constant value in a constant” nghĩa là “cố dùng một giá trị không phải hằng trong một hằng”. Đâ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 days = 7; let rainfall = [0.0; days]; // ❌ an array's length must be known at compile time println!("{:?}", rainfall);}Ý nghĩa
Phần tiêu đề “Ý nghĩa”Dấu ^^^^ chỉ vào days ở dòng 3, bên trong [0.0; days]. Đó là dạng “lặp lại” của mảng: bảy bản sao của 0.0. Phần sau dấu ; là độ dài của mảng, và độ dài là một phần của kiểu mảng: [f64; 7] và [f64; 8] là hai kiểu khác nhau. Vì vậy rustc phải biết con số này ngay khi biên dịch.
Nhưng days là một biến let. Với rustc, một let là giá trị chỉ tồn tại khi chương trình chạy, kể cả khi bạn viết một số 7 trơn bên cạnh nó. “Non-constant value” (giá trị không phải hằng) nghĩa chính xác là vậy.
Dòng help: cho thấy cách sửa: biến dòng 2 thành một const. Hãy để ý chỗ giữ chỗ /* Type */: một const luôn cần ghi rõ kiểu, nên bạn sẽ viết const DAYS: usize = 7;.
Cùng dòng tiêu đề này cũng xuất hiện khi một const hoặc static đọc một biến let, như const PRICE_WITH_TAX: f64 = base_price * 1.2;. Khi đó help: gợi ý theo hướng ngược lại: dùng let thay cho const.
Giống như in một tờ phiếu. Số ô trên phiếu được quyết định ở nhà in, trước khi có ai điền vào. Bạn không thể để số ô phụ thuộc vào câu trả lời mà ai đó sẽ viết lên phiếu sau này.
Vì sao Rust làm vậy
Phần tiêu đề “Vì sao Rust làm vậy”Rust có hai thời điểm: lúc biên dịch (compile time), khi cargo build biến code thành chương trình, và lúc chạy (run time), khi chương trình chạy. const, static và độ dài của mảng đều được quyết định lúc biên dịch. Trình biên dịch ghi thẳng chúng vào chương trình. Đó là lý do chúng nhanh và dùng được ở bất cứ đâu.
Một biến let thuộc về lúc chạy. Giá trị của nó có thể đến từ file, từ dữ liệu người dùng nhập, hoặc từ một phép tính chỉ diễn ra khi code chạy. Rust không ngoại lệ cho let days = 7; chỉ vì lần này nó tình cờ là một literal: quy tắc giữ được sự đơn giản, và code của bạn không hỏng vào ngày bạn đổi 7 thành read_days_from_config().
Vậy câu cần hỏi là: mình có biết con số này ngay khi viết code không? Nếu có, hãy biến nó thành const. Nếu không, nó không thể làm độ dài mảng, và Vec mới là công cụ đúng. Xem Hằng số và Mảng so với vector.
Cách sửa
Phần tiêu đề “Cách sửa”Cách sửa 1: Biến kích thước thành const
Phần tiêu đề “Cách sửa 1: Biến kích thước thành const”Chọn cách này khi con số cố định trong code, như 7 ngày của một tuần. Làm theo help:, với hai thay đổi nhỏ: ghi rõ kiểu (usize cho độ dài) và dùng tên VIẾT_HOA, phong cách Rust dành cho hằng số:
const DAYS: usize = 7; // ✅ đã biết lúc biên dịch
fn main() { let mut rainfall = [0.0; DAYS]; rainfall[2] = 4.5; rainfall[5] = 1.0; println!("{:?}", rainfall); println!("{} ngày", rainfall.len());}Kết quả:
[0.0, 0.0, 4.5, 0.0, 0.0, 1.0, 0.0]7 ngàyBạn cũng có thể đặt const bên trong main, ngay chỗ của let trước đó. Đặt ở đầu file thì hàm nào cũng dùng được. Nếu bạn giữ tên viết thường days, code vẫn biên dịch, nhưng kèm cảnh báo rằng hằng số nên viết hoa.
Cách sửa 2: Dùng Vec khi kích thước chỉ biết lúc chạy
Phần tiêu đề “Cách sửa 2: Dùng Vec khi kích thước chỉ biết lúc chạy”Chọn cách này khi con số đến từ chương trình đang chạy: độ dài của một danh sách, một giá trị đọc từ file, dữ liệu người dùng nhập, một tham số hàm. Mảng có kích thước cố định; Vec là danh sách có thể lớn lên và có kích thước bất kỳ. vec![value; n] là cùng dạng “lặp lại” đó, và ở đây n có thể là bất kỳ biến nào:
fn main() { let players = ["Ana", "Ben", "Chen"]; let rounds = players.len() * 2;
let mut scores = vec![0; rounds]; // ✅ độ dài của Vec có thể lấy từ một biến scores[0] = 10; scores[3] = 7; println!("{:?}", scores); println!("{} vòng", scores.len());}Kết quả:
[10, 0, 0, 7, 0, 0]6 vòngĐây là cách sửa đúng thường xuyên hơn nhiều so với người ta nghĩ. Vec dùng được ở gần như mọi chỗ mà mảng dùng được: truy cập theo chỉ số, .len(), vòng lặp, slice (&scores[1..3]). Khác biệt về tốc độ là rất nhỏ với chương trình thông thường. Xem Vector.
Cách sửa 3: Dùng let thay cho const
Phần tiêu đề “Cách sửa 3: Dùng let thay cho const”Chọn cách này khi một const (hoặc static) đọc một biến let. Phiên bản này của lỗi trông như sau:
error[E0435]: attempt to use a non-constant value in a constant --> src/main.rs:3:33 |3 | const PRICE_WITH_TAX: f64 = base_price * 1.2; // ❌ a `const` can't read a `let` variable | ^^^^^^^^^^ non-constant value |help: consider using `let` instead of `const`Chương trình bị lỗi:
fn main() { let base_price = 20.0; const PRICE_WITH_TAX: f64 = base_price * 1.2; // ❌ a `const` can't read a `let` variable println!("{:.2}", PRICE_WITH_TAX);}Nếu giá trị được tính trong lúc chương trình chạy, nó là một biến bình thường, vậy hãy dùng let (với tên viết thường). Nếu cả hai giá trị thật sự cố định, cách ngược lại cũng được: biến cả giá trị đầu tiên thành const. Cả hai cách được thể hiện ở đây:
const BASE_PRICE: f64 = 20.0;const PRICE_WITH_TAX: f64 = BASE_PRICE * 1.2; // ✅ const dùng được const khác
fn main() { let base_price = 35.0; let price_with_tax = base_price * 1.2; // ✅ let dùng được mọi thứ println!("{:.2}", price_with_tax); println!("{:.2}", PRICE_WITH_TAX);}Kết quả:
42.0024.00Dòng help: gợi ý let PRICE_WITH_TAX; như vậy biên dịch được, nhưng bạn sẽ nhận cảnh báo rằng biến nên viết theo snake_case. Hãy đổi tên thành price_with_tax.
Cách sửa 4: Const generic hoặc const fn, khi bạn thật sự cần mảng
Phần tiêu đề “Cách sửa 4: Const generic hoặc const fn, khi bạn thật sự cần mảng”Chọn cách này khi bạn muốn một mảng có kích thước cố định thật sự, nhưng kích thước lại đến từ bên ngoài hàm, hoặc cần được tính ra. Tham số hàm cũng là giá trị lúc chạy, nên đoạn này gây ra E0435 (rustc nói tham số đó would need to be a const, cần phải là một const):
error[E0435]: attempt to use a non-constant value in a constant --> src/main.rs:2:21 |1 | fn empty_row(size: usize) { | ---- this would need to be a `const`2 | let row = [0u8; size]; // ❌ `size` is only known when the function runs | ^^^^Chương trình bị lỗi:
fn empty_row(size: usize) { let row = [0u8; size]; // ❌ `size` is only known when the function runs println!("{:?}", row);}
fn main() { empty_row(3);}Một const generic làm đúng điều rustc yêu cầu: <const N: usize> biến kích thước thành một tham số lúc biên dịch, được viết là empty_row::<5>() hoặc được suy ra từ kiểu. Một const fn là hàm mà trình biên dịch có thể chạy lúc biên dịch, nên bạn dùng nó để tính ra một hằng số:
fn empty_row<const N: usize>() -> [u8; N] { [0; N] // ✅ N là một tham số lúc biên dịch}
const fn cells(width: usize, height: usize) -> usize { width * height}
const WIDTH: usize = 4;const HEIGHT: usize = 3;const CELLS: usize = cells(WIDTH, HEIGHT); // ✅ do trình biên dịch tính ra
fn main() { let row: [u8; 3] = empty_row(); let wide = empty_row::<5>(); println!("{:?}", row); println!("{:?}", wide);
let board = [0u8; CELLS]; println!("{} ô", board.len());}Kết quả:
[0, 0, 0][0, 0, 0, 0, 0]12 ôCó một giới hạn trên Rust stable: bên trong hàm generic, bạn dùng được N đứng một mình làm độ dài, nhưng không dùng được một phép tính với nó, như [u8; N * 2]. Trường hợp đó cho ra generic parameters may not be used in const operations và cần một tính năng chỉ có trên nightly. Nếu chính bạn là người gọi empty_row(size) với một size lúc chạy, hãy quay lại cách sửa 2. Xem Generic.
Thường gặp ở đâu
Phần tiêu đề “Thường gặp ở đâu”Kích thước lấy từ .len() hoặc dữ liệu người dùng nhập. let n = names.len(); let done = [false; n]; gây ra E0435, vì n chỉ biết được lúc chạy. Cách sửa: let done = vec![false; n]; (cách sửa 2).
Một static đọc một let. static LINE_WIDTH: usize = width; cho ra cùng dòng tiêu đề, kèm help: consider using `let` instead of `static` . Một static được quyết định lúc biên dịch y như const. Cách sửa: dùng let, hoặc biến cả width thành const.
Một const bên trong hàm generic. Trong fn bytes<T>() -> usize { const SIZE: usize = std::mem::size_of::<T>(); SIZE }, const không dùng được T. Đó là một mã khác, E0401, và cách sửa cùng một ý tưởng: let size = std::mem::size_of::<T>();.
Lỗi liên quan
Phần tiêu đề “Lỗi liên quan”- E0434: can’t capture dynamic environment in a fn item: cùng quy tắc “mục (item) không nhìn thấy biến cục bộ”, cho một
fnviết bên trong hàm khác. - E0401: can’t use generic parameters from outer item: một
fnhoặcconstlồng bên trong dùngTcủa hàm bên ngoài. - E0015:
cannot call non-const function `args` in constants, khi mộtconstgọi một hàm thường nhưstd::env::args()thay vì đọc biến. Cách sửa:let, hoặc mộtconst fn. - E0308: mismatched types: lỗi bạn gặp sau cách sửa 1 nếu một mảng sai độ dài được đưa vào chỗ cần
[f64; DAYS]. - E0658: use of unstable feature: nếu bạn tìm tới các tính năng const chỉ có trên nightly để lách lỗi này.
- Tất cả lỗi trình biên dịch
Kiểm tra nhanh
Phần tiêu đề “Kiểm tra nhanh”Kiểm tra nhanh
1. Vì sao let n = 3; let a = [0; n]; gây ra E0435, dù n rõ ràng là 3?
2. Kích thước danh sách của bạn đến từ một file mà chương trình đọc. Cách sửa đúng là gì?
3. Dòng nào biên dịch được?
Điểm: 0 / 3
Đọc thêm
Phần tiêu đề “Đọc thêm”- E0435 — Rust error codes index (tiếng Anh): phần giải thích chính thức, với cách sửa bằng
const. - Constants — The Rust Book (tiếng Anh):
constkhácletthế nào, và vì sao hằng số cần ghi kiểu. - Constant evaluation — The Rust Reference (tiếng Anh): chính xác những biểu thức nào trình biên dịch tính được lúc biên dịch, và những chỗ bắt buộc phải có chúng (độ dài mảng,
const,static). - Storing Lists of Values with Vectors — The Rust Book (tiếng Anh): danh sách có thể lớn lên, dùng khi kích thước chỉ biết lúc chạy.
- Const generics MVP — Rust Blog (tiếng Anh):
<const N: usize>hoạt động thế nào, với các ví dụ về mảng, và Rust stable làm được gì, không làm được gì với nó.