콘텐츠로 이동

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

이 에러를 내는 프로그램은 다음과 같아요.

fn main() {
let days = 7;
let rainfall = [0.0; days]; // ❌ 배열 길이는 컴파일할 때 알아야 해요
println!("{:?}", rainfall);
}

^^^^는 3번째 줄, [0.0; days] 안의 days를 가리켜요. 이건 배열의 “반복” 형태로, 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:는 반대 방향을 제안해요. const 대신 let을 쓰라고요.

신청서를 인쇄하는 것과 비슷해요. 신청서에 칸이 몇 개 있을지는 누가 내용을 채우기 전에, 인쇄소에서 정해져요. 나중에 누군가 신청서에 적을 답에 따라 칸 수를 바꿀 수는 없어요.

Rust에는 두 시점이 있어요. cargo build가 코드를 프로그램으로 바꾸는 컴파일 타임, 그리고 그 프로그램이 돌아가는 런타임이에요. const, static, 배열 길이는 모두 컴파일 타임에 정해져요. 컴파일러가 그 값을 프로그램 안에 바로 써 넣어요. 그래서 빠르고, 어디서든 쓸 수 있어요.

let 변수는 런타임에 속해요. 그 값은 파일에서 올 수도 있고, 사용자 입력에서 올 수도 있고, 코드가 실행될 때만 하는 계산에서 올 수도 있어요. let days = 7;이 마침 리터럴이라고 해서 Rust가 예외를 두지는 않아요. 그래야 규칙이 단순하게 유지되고, 언젠가 7을 read_days_from_config()로 바꾸는 날에도 코드가 갑자기 깨지지 않아요.

그러니 스스로 물어볼 질문은 하나예요. 코드를 쓰는 지금, 이 숫자를 알고 있나요? 알고 있다면 const로 만드세요. 모른다면 배열 길이가 될 수 없고, Vec이 알맞은 도구예요. 상수와 배열과 벡터를 참고하세요.

한 주의 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 쓰기

섹션 제목: “방법 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(), 반복문, 슬라이스(&scores[1..3])까지요. 평범한 프로그램에서는 속도 차이도 아주 작아요. 벡터를 참고하세요.

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 쓰기

섹션 제목: “방법 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개
▶ 직접 실행해 보기

stable Rust에는 한 가지 제한이 있어요. 제네릭 함수 안에서 N을 그대로 길이로 쓰는 건 되지만, [u8; N * 2]처럼 N으로 계산한 값은 안 돼요. 그러면 generic parameters may not be used in const operations(제네릭 매개변수는 const 연산에 쓸 수 없다)라는 에러가 나고, 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];은 n이 분명히 3인데도 왜 E0435를 낼까요?

2. 리스트의 크기가 프로그램이 읽는 파일에서 와요. 올바른 해결책은 무엇일까요?

3. 어느 줄이 컴파일될까요?

점수: 0 / 3