역사: const 제네릭은 Rust 1.51에서 정수형 기본 값으로 매개변수화할 수 있도록 안정화되어 **[T; N]**과 같은 일반적인 고정 크기 배열을 가능하게 했습니다. 설계 단계에서 언어 팀은 구조적 동등성과 결정론적인 컴파일 타임 평가를 나타내는 유형에만 const 제네릭 매개변수를 명시적으로 제한했습니다. 이러한 제한으로 인해 총 정렬을 위반하거나 런타임 메모리 주소에 의존하는 f32, f64 및 &str 리터럴이 제외되었습니다.
문제: 부동 소수점 유형의 핵심 문제는 반사적 동등성을 위반하는 NaN(숫자가 아님)의 존재로, 이는 단형화 중 타입 식별을 신뢰할 수 없도록 만듭니다(NaN != NaN). 문자열 리터럴(&str)의 경우, 문제는 그들의 두꺼운 포인터 표현(주소 + 길이)과 데이터 세그먼트에서의 특정 메모리 주소 의존성에 있습니다. 이는 컴파일 유닛 간 또는 크레이트에 걸쳐 결정론적이지 않습니다. 타입 시스템은 **MyStruct<1>**과 **MyStruct<1>**이 항상 동일한 타입을 참조해야 하며, 이는 const 매개변수의 동등성이 컴파일 타임에 비트 단위 또는 구조적 비교를 통해 결정될 수 있어야 한다고 요구합니다.
해결책: Rust 컴파일러는 HIR(고급 중간 표현) 낮추기 및 타입 검사 중에 StructuralPartialEq(불안정)와 같은 내부 특성을 통해 이러한 제약을 시행합니다. const 제네릭 매개변수를 만났을 때, 컴파일러는 해당 유형이 정수, bool 또는 char일 수 있거나 구조적 동등성을 지원한다고 명시적으로 마킹된 사용자 정의 타입이어야 함을 확인합니다. 부동 소수점 유형은 그들의 동등성이 반사적이지 않기 때문에 거부되며, &str과 같은 참조는 생애와 간접성을 도입하기 때문에 const 제네릭에 필요한 'static' 컨텍스트에서 조화될 수 없습니다. 단형화 중에 컴파일러는 const 표현식을 평가하고 구조적 동등성을 사용하여 동일한 인스턴스를 병합하여 타입 안전성을 보장합니다.
// 유효: usize는 구조적 동등성을 가집니다. struct Matrix<const N: usize> { data: [[f64; N]; N], } // 유효하지 않음: f64는 총 정렬이 부족합니다(NaN 문제) // struct Physics<const G: f64>; // 오류: 부동 소수점 유형은 const 제네릭에 사용할 수 없습니다. // 유효하지 않음: &str은 간접성과 생애 복잡성을 가집니다. // struct Label<const S: &str>; // 오류: `&str`은 const 제네릭 매개변수 유형으로 금지되어 있습니다.
당신은 금융 상품이 계약 사양을 위한 컴파일 타임 상수 매개변수를 가져야 하는 고주파 거래 엔진을 설계하고 있습니다. 예: 틱 크기(예: 0.25 USD) 또는 배수 계수. 초기 설계는 이러한 정밀한 소수 값을 타입 시스템에 직접 인코딩하기 위해 f64 const 제네릭을 사용하는 것을 시도했습니다. 이는 이러한 상수의 런타임 저장을 제거하고 가격 계산의 컴파일 타임 최적화를 가능하게 할 것으로 기대했습니다.
고려된 접근 방법 중 하나는 f64 비트를 u64로 변환하여 const 매개변수로 사용하고, 구현 내에서 다시 변환하는 것이었습니다. 그러나 이는 부호 있는 0(+0.0 vs -0.0) 및 NaN 페이로드로 인해 서로 다른 의미 값을 나타낼 수 있기 때문에 위험한 것으로 판명되었습니다. 이것은 컴파일러가 서로 다른 금융 상품을 동일한 유형으로 처리하거나 서로 다른 NaN 페이로드를 가진 타입을 잘못 병합하도록 만들어 가격 논리가 잘못될 수 있습니다.
또 다른 해결책은 trait 내에 연관 상수를 사용하는 것이었습니다(trait Instrument { const TICK_SIZE: f64; }). 이는 부동 소수점 값을 허용하지만 틱 크기를 타입 수준 구분자로 사용하려는 능력을 희생합니다. 이는 서로 다른 틱 크기를 가진 다양한 상품을 포함하는 **Vec<Instrument<TICK_SIZE>>**를 만들 수 없습니다. 이는 dyn Trait 객체의 오버헤드를 초래하며, 이는 핫 경로에서 허용될 수 없는 vtable 간접을 도입합니다.
선택된 해결책은 부동 소수점 값을 고정 소수점 정수(예: 0.25 USD를 크기 조정 계수 100과 함께 크기 조정된 usize 25로 나타내는 것)로 인코딩하는 것이었습니다. 이 접근 방법은 const 제네릭 제약 조건을 만족하면서 제로 비용 추상화와 컴파일 타임 평가를 유지합니다. 결과적으로 Bond<25>와 Bond<50>은 런타임 오버헤드 없이 별개의 타입을 가진 타입 안전 계약 시스템이 되었지만, 산술 오류를 방지하기 위한 큰 문서 작업이 필요했습니다.
Rust가 char 및 bool을 const 제네릭 매개변수로 허용하지만 &str을 제외하는 이유는 무엇입니까? 두 가지 모두 기술적으로 기본 타입으로 간주될 수 있습니다.
char 및 bool은 고정 크기의 값 타입이며 간단한 구조적 동등성을 가지고 있습니다. char는 32비트 유니코드 스칼라 값이고 bool은 엄격히 0 또는 1이기 때문에 비트 단위 비교가 가능합니다. &str은 데이터 포인터와 길이를 포함하는 두꺼운 포인터(또는 DST에 대한 참조)로, 간접성과 생애 매개변수를 도입합니다. 컴파일러는 두 문자열 리터럴이 서로 다른 크레이트 간에 동일한 메모리 주소에 존재한다는 보장을 할 수 없으며, 그들의 생애가 'static 요구 사항을 만족하도록 보장하기도 힘듭니다. 따라서 &str은 const 제네릭 매개변수에 필요한 구조적 속성이 부족하며, 반면에 char과 bool은 자족하는 값입니다.
부동 소수점 유형에 대한 const 제네릭 구현이 NaN(숫자가 아님) 값과 관련하여 타입 안전성을 어떻게 깨뜨릴 가능성이 있습니까?
f32가 허용되면 MyStructf32::NAN 및 **MyStruct<{ 0.0 / 0.0 }>**와 같은 표현식이 모두 NaN 값을 생성하지만, 컴파일러는 이러한 값이 동일한 타입을 나타내는지 보장할 수 없습니다. 이는 논리적으로 동일해야 하는 두 개의 서로 다른 단형화를 만들 수 있거나, 반대로 서로 다른 NaN 페이로드를 포함한 유형을 잘못 병합할 수 있도록 합니다. 타입 식별의 이러한 위반은 싱글턴 패턴이 실패하게 하거나, 타입 기반 최적화가 잘못된 코드를 생성하는 경우로 이어질 수 있습니다. 컴파일러는 타입 매개변수가 각각의 타입을 고유하게 식별한다고 가정하기 때문입니다.
const 제네릭과 연관 상수의 근본적인 차이점은 무엇이며, 전자는 왜 구조적 동등성을 요구하고 후자는 요구하지 않습니까?
Const 제네릭 매개변수는 타입 식별의 일부입니다; **Container<10>**과 **Container<20>**은 서로 다른 유형으로 별도의 단형화를 가집니다. 이는 값이 컴파일 타임에 비교 가능해야 한다는 것을 요구하여 전역 고유성을 보장하고 동일한 인스턴스를 병합할 수 있도록 합니다. 연관 상수는 타입 구현과 관련된 값이지만 타입 자체를 변경하지 않습니다. TypeA와 TypeB는 연관 상수 값에 관계없이 여전히 서로 다른 유형입니다. 따라서 연관 상수는 부동 소수점이나 복합 유형이 될 수 있습니다. 왜냐하면 그것들은 단순히 구현 내에서 값을 제공할 뿐이며, 타입 검사 또는 단형화에 영향을 미치지 않으며, 타입 시스템 수준에서 구조적 동등성의 필요성을 우회하기 때문입니다.