2.11. const 제네릭
const 제네릭은 제네릭 매개변수 자리에 값을 받는 형태입니다. 컴파일 타임에 결정되는 정수, 진리값, 짧은 표현식을 매개변수로 두는 방식입니다.
2.11.1. 동기
배열 길이, 행렬 차원, 임베디드 메모리 풀 크기처럼 컴파일 타임에 결정되는 값을 타입의 일부로 가져가기 위해서입니다.
struct Matrix<T, const M: usize, const N: usize> {
data: [T; M * N];
}
fn dot<T: Num, const N: usize>(a: [T; N], b: [T; N]) -> T { ... }
이렇게 하면 배열 길이가 타입의 일부가 되므로 호출 시 길이 체크 비용이 0 이 됩니다. 또한 컴파일러가 SIMD 나 언롤링처럼 길이에 의존하는 최적화를 적용할 수 있고, 행렬 곱처럼 차원 호환성이 중요한 연산을 컴파일 타임에 검증할 수 있습니다.
2.11.2. 선언 형태
const 매개변수이름 : 타입
const 키워드 다음에 매개변수 이름과 :, 그리고 타입이 차례로 옵니다. 이때 타입은 아래에서 설명할 허용된 const 타입이어야 합니다.
여러 매개변수를 섞을 때는 콤마로 구분합니다. 타입 매개변수와 const 매개변수는 자유롭게 섞일 수 있습니다.
fn replicate<T, const N: usize>(value: T) -> [T; N] { ... }
struct Tagged<T, const TAG: u32> { value: T; }
2.11.3. 허용 타입
const 매개변수의 타입은 정수형 (i8 ~ i64, u8 ~ u64, usize, isize), bool, char 중 하나입니다.
반면 부동소수, 합 타입, 사용자 정의 구조체는 const 매개변수가 될 수 없습니다. 부동소수는 비트 표현의 비결정성 때문에 제외되고, 사용자 정의 타입은 동등 비교의 일관성 문제 때문에 제외됩니다.
2.11.4. 인스턴스화 시 인자
호출이나 인스턴스화 지점에서 const 매개변수에 const 표현식을 넘깁니다.
let m: Matrix<f64, 3, 4> = ...;
let v: [i32; 16] = ...;
let arr = replicate::<f64, 8>(0.0f64);
이때 인자는 컴파일 타임에 평가 가능해야 합니다. 구체적으로는 리터럴, const 변수, const 함수 호출, 다른 const 매개변수, 그리고 이들의 단순 산술 조합이 허용됩니다. 반면 런타임 변수는 const 인자 자리에 쓸 수 없으며, 그 경우 컴파일 에러가 됩니다.
2.11.5. const 표현식의 산술
const 매개변수 사이의 단순 산술을 타입 안에서 쓸 수 있습니다.
struct Matrix<T, const M: usize, const N: usize> {
data: [T; M * N];
}
fn flatten<T, const M: usize, const N: usize>(
m: Matrix<T, M, N>
) -> [T; M * N] { ... }
허용되는 연산자는 +, -, *, /, %, 그리고 단항 - 입니다. 비교나 조건분기는 현 사양에서 허용하지 않으며, 단순 산술만 가능합니다. 다만 타입 시스템은 M * N 과 N * M 이 같은 값임을 인식합니다 (가환성).
이보다 더 복잡한 const 표현식, 예를 들어 조건분기나 재귀 같은 형태는 const 함수 사양에서 다룹니다.
2.11.6. 매개변수 추론
const 인자는 인자의 타입, 구조체 필드의 타입, 기대 타입 (let 주석, 매개변수 타입, 반환 타입) 으로부터 추론할 수 있으면 명시를 생략할 수 있습니다.
fn replicate<T, const N: usize>(value: T) -> [T; N] { ... }
let arr: [i32; 8] = replicate(0i32); // N = 8 추론
let arr2 = replicate::<i32, 8>(0i32); // 명시
추론 우선순위와 모호성 해소는 표현식 사양에서 정의합니다.
반복 리터럴 [v; N]
[v; N] 은 값 v 를 N 번 반복한 [T; N] 배열입니다. N 은 const 표현식이며 const 매개변수일 수 있습니다.
fn zeros<const N: usize>() -> [f64; N] { [0.0; N] }
let z: [f64; 4] = zeros(); // N = 4 - 기대 타입에서
let w = [1u8; 16]; // [u8; 16]
2.11.7. 단형화
const 제네릭은 제네릭 의 단형화와 같은 방식으로 동작합니다. 따라서 각 const 인자 조합마다 별개의 컴파일 결과가 만들어집니다.
let a = replicate(0i32); // N = ? - 추론 시점에 결정
같은 const 인자 조합의 호출은 결과를 공유합니다.
2.11.8. 동등성
const 인자가 같은 값을 갖는 두 타입은 같은 타입입니다.
let a: [i32; 4] = ...;
let b: [i32; 2 + 2] = ...;
// a 와 b 는 같은 타입
이러한 동등성은 const 표현식 평가가 결정론적이기 때문에 가능합니다.
2.11.9. 정의 가능 위치
const 제네릭은 다음 모든 위치에서 같은 문법으로 쓸 수 있습니다. 함수와 메서드에서 사용할 수 있고, 구조체와 enum, union 에서도 쓸 수 있습니다. 그 밖에 트레잇, impl 블록, 타입 별칭에서도 동일하게 사용할 수 있습니다.
이러한 균일성은 제네릭 의 일반 제네릭과 일치하므로, 사용자가 따로 외워야 할 규칙이 없습니다.
2.11.10. 인덱스로 사용
const 매개변수는 단순한 const 변수처럼 값 위치에서 그대로 쓸 수 있습니다.
fn first<T, const N: usize>(arr: [T; N]) -> T {
arr[0]
}
fn middle<T, const N: usize>(arr: [T; N]) -> T {
arr[N / 2]
}
값 위치에서의 정확한 사용 규칙은 표현식 사양에서 정의합니다.