Geschiedenis: Const generics zijn gestabiliseerd in Rust 1.51 om types te parametriseren met constante waarden van primitieve gehele types, waardoor generieke arrays met vaste grootte zoals [T; N] mogelijk worden. Tijdens de ontwerpfase heeft het taalteam expliciet de const generieke parameters beperkt tot types die structurele gelijkheid en deterministische compilatietijd evaluatie vertonen. Deze beperking sluit f32, f64 en &str literals uit vanwege hun schending van totale ordening of hun afhankelijkheid van tijdsgebonden geheugenadressen.
Probleem: Het kernprobleem met drijvende-komma types is de aanwezigheid van NaN (Not-a-Number), die reflexieve gelijkheid schendt (NaN != NaN), waardoor de compiler type-identiteit niet betrouwbaar kan bepalen tijdens de monomorfisatie. Voor string literals (&str) ligt het probleem in hun vetpointerrepresentatie (adres + lengte) en hun afhankelijkheid van specifieke geheugenadressen in het gegevenssegment, die niet deterministisch zijn tussen compilatie-eenheden of crates. Het type-systeem vereist dat MyStruct<1> en MyStruct<1> altijd naar hetzelfde type verwijzen, wat vereist dat de gelijkheid van de const parameter besluitbaar is via bitwijze of structurele vergelijking tijdens de compilatietijd.
Oplossing: De Rust compiler handhaaft deze beperkingen via interne traits zoals StructuralPartialEq (instabiel) tijdens het verlagen en type controleren van HIR (High-Level Intermediate Representation). Wanneer de compiler een const generieke parameter tegenkomt, controleert deze of het type een geheel getal, bool, of char is, of een door de gebruiker gedefinieerd type dat expliciet is gemarkeerd als ondersteunend aan structurele gelijkheid. Het wijst drijvende-komma types af omdat hun gelijkheid niet reflexief is, en wijst verwijzingen zoals &str af omdat ze levensduurcomplexiteit en indirectie introduceert die niet kan worden verzoend in de 'static context die vereist is voor const generics. Tijdens de monomorfisatie evalueert de compiler const-expressies en gebruikt structurele gelijkheid om identieke instantiaties samen te voegen, wat typeveiligheid garandeert.
// Geldig: usize heeft structurele gelijkheid struct Matrix<const N: usize> { data: [[f64; N]; N], } // Ongeldig: f64 mist totale ordening (NaN-problemen) // struct Physics<const G: f64>; // Fout: drijvende-komma types kunnen niet worden gebruikt in const generics // Ongeldig: &str heeft indirectie en levensduurcomplexiteit // struct Label<const S: &str>; // Fout: `&str` is verboden als type van een const generieke parameter
Je architecteert een high-frequency trading engine waar financiële instrumenten compile-tijd constante parameters moeten bevatten voor contractspecificaties, zoals tickgroottes (bijv. 0,25 USD) of vermenigvuldigingscoëfficiënten. Het initiële ontwerp probeerde f64 const generics te gebruiken om deze precieze decimale waarden direct in het type-systeem te coderen, in de hoop runtime opslag van deze constanten te elimineren en compile-tijd optimalisatie van prijsberekeningen mogelijk te maken.
Een overweging was om de beperking te omzeilen door f64 bits om te zetten naar u64 en dat als de const parameter te gebruiken, en vervolgens weer om te zetten binnen de implementatie. Dit bleek echter riskant omdat bitgewijs identieke floats verschillende semantische waarden kunnen vertegenwoordigen vanwege ondertekende nul (+0.0 vs -0.0) en NaN payloads, wat ertoe kan leiden dat de compiler verschillende financiële instrumenten als hetzelfde type behandelt of berekeningen samenvoegt die gescheiden moeten blijven, wat leidt tot onjuiste prijslogica.
Een andere oplossing hield in dat geassocieerde constanten binnen een trait (trait Instrument { const TICK_SIZE: f64; }) werden gebruikt. Hoewel dit drijvende-komma waarden toestaat, biedt het geen mogelijkheid om de tick-grootte als een type-niveau discriminator te gebruiken; je kunt geen Vec<Instrument<TICK_SIZE>> hebben die verschillende instrumenten met verschillende tick-groottes bevat zonder gebruik te maken van dyn Trait object overhead, wat een vtable-indirectie introduceert die onaanvaardbaar is in het snelste pad.
De gekozen oplossing was om de drijvende-komma waarden te coderen als vaste-getal gehele (bijv. het vertegenwoordigen van 0,25 USD als de usize 25 met een impliciete schaalfactor van 100). Deze benadering voldoet aan de const generieke beperkingen terwijl het nul-kosten abstractie en compile-tijd evaluatie behoudt. Het resultaat was een type-veilige contract systeem waar Bond<25> en Bond<50> verschillende types zijn zonder runtime overhead, hoewel het zorgvuldige documentatie van de schaalconventie vereiste om rekenkundige fouten te voorkomen.
Waarom staat Rust char en bool toe als const generieke parameters maar sluit &str uit, gezien beide technisch primitieve types zijn?
Char en bool zijn waarde types met vaste groottes en triviale structurele gelijkheid; een char is een 32-bits Unicode scalar waarde en bool is strikt 0 of 1, waardoor bitwijze vergelijking mogelijk is. &str is een vetpointer (of verwijzing naar een DST) die een gegevenspointer en een lengte bevat, wat indirectie en levensduurparameters introduceert. De compiler kan niet garanderen dat twee string literals zich op hetzelfde geheugenadres bevinden in verschillende crates of dat hun levensduur voldoet aan de 'static vereisten op een manier die type-identiteitscontrole mogelijk maakt. Dienovereenkomstig mist &str de structurele eigenschappen die vereist zijn voor const generieke parameters, terwijl char en bool zelfcontainende waarden zijn.
Hoe zou het implementeren van const generics voor drijvende-komma types potentieel de typeveiligheid schenden met betrekking tot NaN (Not-a-Number) waarden?
Als f32 zou worden toegestaan, zouden expressies zoals MyStructf32::NAN en MyStruct<{ 0.0 / 0.0 }> beide NaN waarden produceren, maar de compiler kon niet garanderen dat ze hetzelfde type vertegenwoordigden omdat NaN != NaN. Dit zou het creëren van twee verschillende monomorfisaties toestaan van wat logisch hetzelfde type zou moeten zijn, of omgekeerd, de compiler dwingen om onjuist types samen te voegen die verschillende NaN payloads bevatten. Deze schending van type-identiteit zou kunnen leiden tot ongeldigheid waar singleton-patronen falen of waar type-gebaseerde optimalisaties onjuiste code produceren, omdat de compiler aanneemt dat typeparameters uniek een enkel type identificeren.
Wat is het fundamentele onderscheid tussen const generics en geassocieerde constanten, en waarom vereist de eerste structurele gelijkheid terwijl de laatste dat niet doet?
Const generieke parameters maken deel uit van de type-identiteit; Container<10> en Container<20> zijn verschillende types met aparte monomorfisaties. Dit vereist dat de waarden compile-tijd vergelijkbaar zijn om wereldwijde uniekheid te garanderen en om identieke instantiaties samen te voegen. Geassocieerde constanten zijn waarden die zijn geassocieerd met een type-implementatie maar de type zelf niet veranderen; TypeA en TypeB blijven verschillende types, ongeacht hun geassocieerde constante waarden. Daarom kunnen geassocieerde constanten drijvende-komma of complexe types zijn, omdat ze slechts waarden binnen de implementatie bieden zonder type-controle of monomorfisatie te beïnvloeden, en de noodzaak voor structurele gelijkheid op het type-systeemniveau omzeilen.