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);}这个错误是什么意思
Section titled “这个错误是什么意思”^^^^ 指向第 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 为什么要这样做
Section titled “Rust 为什么要这样做”Rust 有两个时刻:编译时,也就是 cargo build 把你的代码变成程序的时候;以及运行时,也就是程序运行的时候。const、static 和数组的长度都是在编译时决定的。编译器会把它们直接写进程序里。正因为这样,它们才很快,而且哪里都能用。
let 变量属于运行时。它的值可能来自文件、用户输入,或者只有在代码运行时才会进行的计算。Rust 不会因为 let days = 7; 碰巧是个字面量就给它开个例外:规则保持简单,而且等哪天你把 7 改成 read_days_from_config() 时,代码也不会突然坏掉。
所以要问自己的问题是:写代码的时候,我知道这个数字吗? 如果知道,就把它写成 const。如果不知道,它就不能当数组长度,这时 Vec 才是合适的工具。参见常量和数组和 vector 的区别。
修复 1:把大小写成 const
Section titled “修复 1:把大小写成 const”当这个数字在代码里是固定的,比如一周有 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。
修复 3:用 let 代替 const
Section titled “修复 3:用 let 代替 const”当一个 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.0024.00help: 那一行建议写成 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>();。
- E0434:can’t capture dynamic environment in a fn item:同样是“项(item)看不到局部变量”这条规则,只不过针对的是写在另一个函数里的
fn。 - E0401:can’t use generic parameters from outer item:嵌套的
fn或const用到了外层函数的T。 - E0015:
cannot call non-const function `args` in constants,当const调用的是std::env::args()这样的普通函数,而不是读取变量时会出现。修复:用let,或者用const fn。 - E0308:mismatched types:用了修复 1 之后,如果把长度不对的数组传给需要
[f64; DAYS]的地方,就会得到这个错误。 - E0658:use of unstable feature:如果你想借助只有 nightly 才有的 const 特性来绕过这个错误。
- 所有编译错误:错误页面的完整列表。
小测验
1. 为什么 let n = 3; let a = [0; n]; 会报 E0435,明明 n 一看就是 3?
2. 列表的大小来自程序读取的文件。正确的修法是什么?
3. 下面哪一行能编译?
得分: 0 / 3
- E0435 — Rust error codes index(英文):这个错误的官方解释,附有改成
const的修法。 - 常量 — Rust 程序设计语言:
const和let有什么不同,以及为什么常量必须写明类型。 - Constant evaluation — The Rust Reference(英文):编译器在编译时到底能算出哪些表达式,以及哪些地方必须用它们(数组长度、
const、static)。 - 使用 Vector 储存列表 — Rust 程序设计语言:大小只有在运行时才知道时,该用的可增长列表。
- Const generics MVP — Rust Blog(英文):
<const N: usize>是怎么工作的,附有数组示例,以及稳定版 Rust 能用它做什么、不能做什么。