How Sway Monomorphization Handles Generic Types at Compile Time
Sway monomorphizes generic types during the type-checking phase by substituting concrete types for generic parameters through a substitution map, creating specialized copies of declarations before LLVM IR generation.
The FuelLabs/sway compiler implements zero-cost abstractions for generic programming through aggressive monomorphization. This process transforms polymorphic code into concrete, type-specific implementations during compilation, ensuring no runtime overhead remains in the final binary.
The Monomorphization Pipeline in Sway
Sway's monomorphization engine operates in four distinct stages defined in sway-core/src/type_system/monomorphization.rs. Each stage progressively transforms generic definitions into concrete type representations.
Stage 1: Building the Substitution Map
The function prepare_type_subst_map_for_monomorphize validates that the number of supplied type arguments matches the generic definition's arity. It resolves each GenericArgument::Type to a concrete TypeId through resolve_type, then constructs a TypeSubstMap pairing generic parameters with their concrete counterparts.
For const generics, the function resolves arguments via resolve_call_path and stores them in a BTreeMap<String, TyExpression> for later materialization.
Stage 2: Adjusting for Implicit Self Types
When monomorphizing trait implementations, Sway handles the implicit Self generic parameter. The adjust_for_trait_decl logic subtracts this hidden parameter from arity checks, ensuring user-facing error messages reference only explicit type arguments. This adjustment occurs at lines 92-99 of monomorphization.rs.
Stage 3: Applying Type Substitutions
The monomorphize_with_modpath function orchestrates the actual transformation. After preparing the substitution map, it invokes value.subst(&SubstTypesContext::new(...)) where the SubstTypes trait (defined in sway-core/src/type_system/subst_types.rs) performs an AST walk. This traversal replaces every occurrence of generic TypeIds with concrete types from the substitution map.
Stage 4: Materializing Const Generics
Following structural substitution, materialize_const_generics transforms const generic arguments into actual TyExpressions. This materialization ensures compile-time constant values are available for subsequent stages, particularly for array layout calculations and memory allocation planning.
Key Implementation Details
Arity Checking and Error Handling
Before substitution begins, the compiler compares value.type_parameters().len() against the supplied type_arguments count. When EnforceTypeArguments::Yes is set and counts mismatch, the compiler emits a CompileError immediately (lines 64-77).
Copy-on-Write Semantics
The DeclEngine manages generic declarations through copy-on-write semantics. When type_decl_opt_to_type_id processes a struct or enum, it clones the original declaration from the engine, runs monomorphize_with_modpath on the copy, then inserts the specialized version back into the engine. This preserves the original generic definition for future instantiations while creating distinct concrete types.
Type Engine Integration
After monomorphization completes, the compiler generates concrete TypeIds via type_engine.insert_struct or insert_enum. The rest of the compilation pipeline—including LLVM IR generation—operates exclusively on these concrete types, eliminating any generic overhead from the final binary.
Handling Special Cases in Sway Monomorphization
Trait Methods and Implicit Self
Trait definitions implicitly carry a Self generic parameter representing the implementing type. During monomorphization of impl blocks, the compiler subtracts this implicit parameter from user-visible arity checks, preventing false mismatch errors while maintaining internal consistency.
Missing Type Arguments
When EnforceTypeArguments::No is specified, the compiler generates default mappings that assign each generic parameter to a fresh, distinct TypeId. This mechanism supports partial type inference and default generic parameters.
Parent-Derived Generics
Generic parameters originating from parent implementations are filtered using !x.is_from_parent() before arity validation. This ensures that only locally relevant generics participate in the monomorphization process for nested or inherited implementations.
Code Examples
Simple Generic Struct
struct Pair<T, U> {
a: T,
b: U,
}
fn main() {
let p: Pair<u64, bool> = Pair { a: 10, b: true };
}
Compilation process:
Pairdefines twoTypeParameters:TandU- The call site supplies
<u64, bool> prepare_type_subst_map_for_monomorphizebuilds the map{ T → u64, U → bool }monomorphize_with_modpathclones thePairdeclaration, substitutes types, and insertsPair<u64, bool>into thetype_engine
The final binary contains a concrete struct definition specialized for u64 and bool with no generic overhead.
Const Generic Array
struct Array<T, const N: u64> {
elems: [T; N],
}
fn main() {
let a: Array<u8, 4> = Array { elems: [0, 1, 2, 3] };
}
Key steps:
- The const argument
4is parsed asGenericArgument::Constand stored in the substitution map'sconstsfield materialize_const_genericsconverts the literal4into aTyExpression- The resulting type
Array<u8, 4>carries a fixed array length[u8; 4]for backend layout calculations
Trait Implementation with Implicit Self
trait Add<Rhs> {
fn add(self, rhs: Rhs) -> Self;
}
impl Add<u64> for u64 {
fn add(self, rhs: u64) -> u64 { self + rhs }
}
Monomorphization details:
- The trait
Addimplicitly carries aSelfgeneric parameter - When monomorphizing the
implblock,prepare_type_subst_map_for_monomorphizesubtracts the implicitSelffrom arity checks viaadjust_for_trait_decl - The substitution map contains
{ Rhs → u64 }whileSelfresolves tou64through the implementation context
Summary
- Sway monomorphization occurs during the type-checking phase, transforming generic definitions into concrete type-specific versions before LLVM IR generation.
- The process centers on
sway-core/src/type_system/monomorphization.rs, specifically theprepare_type_subst_map_for_monomorphizeandmonomorphize_with_modpathfunctions. - Four stages drive the transformation: building substitution maps, adjusting for implicit
Selftypes, applying AST substitutions via theSubstTypestrait, and materializing const generics. - Copy-on-write semantics in the
DeclEngineensure original generic definitions remain available for future instantiations while creating specialized copies. - The implementation guarantees zero-cost abstractions—no generic overhead remains in the final binary since all types are concrete before backend code generation.
Frequently Asked Questions
What is monomorphization in the Sway compiler?
Monomorphization is the compile-time process where the Sway compiler transforms generic code into concrete, type-specific implementations. During the type-checking phase, the compiler substitutes concrete TypeIds for generic parameters, creating specialized copies of structs, enums, and functions. This ensures the final binary contains only concrete types with no runtime generic overhead.
How does Sway handle const generics during monomorphization?
Sway processes const generics through a dedicated materialization step. After building the type substitution map, the compiler stores const arguments in a BTreeMap<String, TyExpression>. The materialize_const_generics function then converts these arguments into actual TyExpressions, making compile-time constant values available for array layout calculations and memory planning in subsequent compilation stages.
Where does the actual type substitution happen in the Sway codebase?
The actual AST transformation occurs in sway-core/src/type_system/subst_types.rs through the SubstTypes trait. The monomorphize_with_modpath function in monomorphization.rs invokes value.subst(&SubstTypesContext::new(...)), which walks the cloned declaration's AST and replaces every generic TypeId with its concrete counterpart from the TypeSubstMap.
Does Sway monomorphization support trait implementations with implicit Self types?
Yes, Sway specifically handles the implicit Self generic parameter in trait implementations. When monomorphizing trait impls, the adjust_for_trait_decl logic subtracts the hidden Self parameter from arity validation checks. This ensures user-facing error messages only reference explicit type arguments while the compiler internally manages the Self type substitution alongside other generic parameters.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →