跳转到内容

Rust 错误 E0435:attempt to use a non-constant value in a constant(常量中使用了非常量值)

E0435 的意思是:某个必须在编译时就确定的东西,用到了一个只有在程序运行时才存在的值。 最常见的地方是数组长度,比如 [0.0; days],或者一个读取 let 变量的 const。这样的代码看起来完全合理,在很多语言里也确实能运行。但在 Rust 里,常量和数组长度是编译器在程序启动之前就算好的,所以它们看不到普通的变量。这是一个很常见的错误,而修复通常只要改一个词。这个系列的其他错误,请看 Rust 编译错误详解。

error[E0435]: attempt to use a non-constant value in a constant
--> src/main.rs:3:26
|
3 | let rainfall = [0.0; days]; // ❌ 数组的长度必须在编译时就确定
| ^^^^ non-constant value
|
help: consider using `const` instead of `let`
|
2 - let days = 7;
2 + const days: /* Type */ = 7;
|

第一行的意思是“试图在常量中使用非常量的值”。help: 建议你“用 const 代替 let”。

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

fn main() {
let days = 7;
let rainfall = [0.0; days]; // ❌ 数组的长度必须在编译时就确定
println!("{:?}", rainfall);
}

^^^^ 指向第 3 行 [0.0; days] 里的 days。这是数组的“重复”写法:7 个 0.0。; 后面的部分是数组的长度,而长度是数组类型的一部分:[f64; 7] 和 [f64; 8] 是两个不同的类型。所以 rustc 在编译时就必须知道这个数字。

可是 days 是一个 let 变量。在 rustc 看来,let 是一个只在程序运行时才存在的值,哪怕你在它旁边写的就是一个简单的 7。“non-constant value”(非常量值)说的正是这个意思。

help: 给出了修法:把第 2 行改成 const。注意那个 /* Type */ 占位符:const 必须写明类型,所以你要写成 const DAYS: usize = 7;。

当 const 或 static 读取一个 let 变量时,也会出现同样的第一行,比如 const PRICE_WITH_TAX: f64 = base_price * 1.2;。这时 help: 的建议方向正好相反:用 let 代替 const。

就像印一张表格。表格上有几个格子,是在印刷厂里决定的,早在有人填写之前。你没法让格子的数量取决于别人以后在表格上写下的答案。

Rust 有两个时刻:编译时,也就是 cargo build 把你的代码变成程序的时候;以及运行时,也就是程序运行的时候。const、static 和数组的长度都是在编译时决定的。编译器会把它们直接写进程序里。正因为这样,它们才很快,而且哪里都能用。

let 变量属于运行时。它的值可能来自文件、用户输入,或者只有在代码运行时才会进行的计算。Rust 不会因为 let days = 7; 碰巧是个字面量就给它开个例外:规则保持简单,而且等哪天你把 7 改成 read_days_from_config() 时,代码也不会突然坏掉。

所以要问自己的问题是:写代码的时候,我知道这个数字吗? 如果知道,就把它写成 const。如果不知道,它就不能当数组长度,这时 Vec 才是合适的工具。参见常量和数组和 vector 的区别。

当这个数字在代码里是固定的,比如一周有 7 天,就选这个。照 help: 说的做,再做两处小改动:写上类型(长度用 usize),并且用 UPPER_CASE(全大写)的名字,这是 Rust 里常量的命名风格:

const DAYS: usize = 7; // ✅ 编译时就知道
fn main() {
let mut rainfall = [0.0; DAYS];
rainfall[2] = 4.5;
rainfall[5] = 1.0;
println!("{:?}", rainfall);
println!("共 {} 天", rainfall.len());
}

输出:

[0.0, 0.0, 4.5, 0.0, 0.0, 1.0, 0.0]
共 7 天
▶ 动手试一试

你也可以把 const 写在 main 里面,就放在原来 let 的位置。写在文件顶部的话,每个函数都能用它。如果保留小写的名字 days,代码照样能编译,但会有一条警告,提醒你常量应该用大写。

修复 2:大小只有在运行时才知道,就用 Vec

Section titled “修复 2:大小只有在运行时才知道,就用 Vec”

当这个数字来自正在运行的程序时,选这个:列表的长度、文件里的值、用户输入、函数参数。数组的大小是固定的;Vec 是可以增长的列表,大小随意。vec![value; n] 是同样的“重复”写法,而这里的 n 可以是任何变量:

fn main() {
let players = ["Ana", "Ben", "Chen"];
let rounds = players.len() * 2;
let mut scores = vec![0; rounds]; // ✅ Vec 的长度可以来自变量
scores[0] = 10;
scores[3] = 7;
println!("{:?}", scores);
println!("共 {} 轮", scores.len());
}

输出:

[10, 0, 0, 7, 0, 0]
共 6 轮
▶ 动手试一试

这个修法适用的场合比大家想象的多得多。凡是能用数组的地方,Vec 几乎都能用:索引、.len()、循环、slice(&scores[1..3])。对普通程序来说,速度上的差别微乎其微。参见 vector。

当一个 const(或 static)读取了一个 let 变量时,选这个。这种情况下的错误长这样:

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; // ❌ `const` 不能读取 `let` 变量
| ^^^^^^^^^^ non-constant value
|
help: consider using `let` instead of `const`

出错的程序:

fn main() {
let base_price = 20.0;
const PRICE_WITH_TAX: f64 = base_price * 1.2; // ❌ `const` 不能读取 `let` 变量
println!("{:.2}", PRICE_WITH_TAX);
}

如果这个值是在程序运行时算出来的,它就是一个普通变量,所以把它写成 let(名字用小写)。如果两个值其实都是固定的,反过来也行:把第一个也写成 const。下面两种写法都有:

const BASE_PRICE: f64 = 20.0;
const PRICE_WITH_TAX: f64 = BASE_PRICE * 1.2; // ✅ const 可以使用另一个 const
fn main() {
let base_price = 35.0;
let price_with_tax = base_price * 1.2; // ✅ let 什么都能用
println!("{:.2}", price_with_tax);
println!("{:.2}", PRICE_WITH_TAX);
}

输出:

42.00
24.00
▶ 动手试一试

help: 那一行建议写成 let PRICE_WITH_TAX;这样能编译,但你会收到一条警告,说变量名应该用 snake_case(小写加下划线)。把它改名为 price_with_tax 就好。

修复 4:确实需要数组时,用 const 泛型或 const fn

Section titled “修复 4:确实需要数组时,用 const 泛型或 const fn”

当你想要一个真正的固定大小数组,但大小应该来自函数外部,或者需要计算出来时,选这个。函数参数也是运行时的值,所以下面的代码会报 E0435(rustc 说这个参数 would need to be a const,需要是一个 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` 要等函数运行时才知道
| ^^^^

出错的程序:

fn empty_row(size: usize) {
let row = [0u8; size]; // ❌ `size` 要等函数运行时才知道
println!("{:?}", row);
}
fn main() {
empty_row(3);
}

const 泛型(const generics)正好做到了 rustc 要求的事:<const N: usize> 让大小变成一个编译时参数,可以写成 empty_row::<5>(),也可以从类型推断出来。const fn 是编译器能在编译时运行的函数,所以你可以用它来计算常量:

fn empty_row<const N: usize>() -> [u8; N] {
[0; N] // ✅ N 是编译时参数
}
const fn cells(width: usize, height: usize) -> usize {
width * height
}
const WIDTH: usize = 4;
const HEIGHT: usize = 3;
const CELLS: usize = cells(WIDTH, HEIGHT); // ✅ 由编译器算出来
fn main() {
let row: [u8; 3] = empty_row();
let wide = empty_row::<5>();
println!("{:?}", row);
println!("{:?}", wide);
let board = [0u8; CELLS];
println!("共 {} 个格子", board.len());
}

输出:

[0, 0, 0]
[0, 0, 0, 0, 0]
共 12 个格子
▶ 动手试一试

稳定版 Rust 有一个限制:在泛型函数里,你可以单独把 N 用作长度,但不能用它参与计算,比如 [u8; N * 2]。那样会报 generic parameters may not be used in const operations(泛型参数不能用在常量运算中),需要一个只有 nightly 才有的特性。如果你就是那个用运行时的 size 去调用 empty_row(size) 的人,那就回到修复 2。参见泛型。

大小来自 .len() 或用户输入。 let n = names.len(); let done = [false; n]; 会报 E0435,因为 n 要到运行时才知道。修复:let done = vec![false; n];(修复 2)。

读取 let 的 static。 static LINE_WIDTH: usize = width; 会给出同样的第一行,还有 help: consider using `let` instead of `static` 。static 和 const 一样,都是在编译时决定的。修复:改用 let,或者把 width 也写成 const。

泛型函数里的 const。 在 fn bytes<T>() -> usize { const SIZE: usize = std::mem::size_of::<T>(); SIZE } 里,这个 const 不能使用 T。这是另一个错误代码 E0401,修法的思路一样:let size = std::mem::size_of::<T>();。

小测验

1. 为什么 let n = 3; let a = [0; n]; 会报 E0435,明明 n 一看就是 3?

2. 列表的大小来自程序读取的文件。正确的修法是什么?

3. 下面哪一行能编译?

得分: 0 / 3