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

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.)

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

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.

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.

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ày
▶ Tự chạy thử

Bạ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
▶ Tự chạy thử

Đâ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.

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.00
24.00
▶ Tự chạy thử

Dò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 ô
▶ Tự chạy thử

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.

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>();.

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