constant (상수)와 const fn

러스트엔 const 키워드가 있습니다. 이름 그대로 상수 선언 키워드며, 얼핏 보면 static item 키워드와 비슷해 보입니다. 주제는 const이기 때문에 static의 간단한 설명과 차이점만 보고 넘어갑시다:

#![allow(unused)]
fn main() {
static STATIC: &str = "Hello, World!";
const CONSTANT: &str = "Hello, World!";
}

둘 모두 &'static str 타입을 가지는 전역 범위에서 사용할 수 있는 상수입니다.

  • static: 수명이 있으며, 가변(mut)이 가능한 변수 (이 경우 unsafe 코드로 값을 변경할 수 있습니다.)
  • const: 변경 불가능. (어떤 일이 있어도 변경할 수 없는 값입니다.)

const fnconst 상수처럼 constant context의 일부입니다. (const impl 등도 이에 포함됩니다.)

이들의 특징은 컴파일 타임 상수 평가자(constant evaluation)가 컴파일 타임에 표현식을 계산합니다. 또한 이들은 for 반복문 등을 허용하지 않습니다. (후술하겠지만, 사실 for 문 그 자체가 문제는 아닙니다.) 그런데 while이나 loop 반복문은 사용할 수 있습니다. 이는 Iteratornext 함수 때문입니다.

for 반복문은 Iteratornext를 호출하여 순회합니다. 하지만 const fn 내부에선 const fn이 아닌 함수를 실행할 수 없습니다. 그렇기에 for 반복문을 사용할 수 없는 것이죠. 때문에 아래의 코드는 작동하지 않습니다:

const fn foo() -> i32 {
    let mut x = 0;
    loop { // work
        if x == 10 {
            break;
        }
        x += 1;
    }
    x
}

const fn bar() -> i32 {
    for x in 0..10 { // <- `for` is not allowed in a `const fn` ...
        if x == 10 {
            break;
        }
    }
}

const FOO: i32 = foo();
const BAR: i32 = bar();

fn main() {
    println!("{}", FOO);
    println!("{}", BAR);
}

miri

추가로, 러스트의 constant contextmiri라는 컴파일러 내장되어있는 인터프리터가 평가합니다.

miriUndefined Behavior (UB) 가 일어나면 컴파일 에러를 띄워주기도 합니다.

평가가 완료되면 바이러니에 바이트채로 저장되어, static 등에 저장됩니다. C++를 해보셨다면, const fn은 C++의 constexpr과 상당히 흡사하다는 걸 알 수 있습니다.