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:

  1. Pair defines two TypeParameters: T and U
  2. The call site supplies <u64, bool>
  3. prepare_type_subst_map_for_monomorphize builds the map { T → u64, U → bool }
  4. monomorphize_with_modpath clones the Pair declaration, substitutes types, and inserts Pair<u64, bool> into the type_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 4 is parsed as GenericArgument::Const and stored in the substitution map's consts field
  • materialize_const_generics converts the literal 4 into a TyExpression
  • 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 Add implicitly carries a Self generic parameter
  • When monomorphizing the impl block, prepare_type_subst_map_for_monomorphize subtracts the implicit Self from arity checks via adjust_for_trait_decl
  • The substitution map contains { Rhs → u64 } while Self resolves to u64 through 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 the prepare_type_subst_map_for_monomorphize and monomorphize_with_modpath functions.
  • Four stages drive the transformation: building substitution maps, adjusting for implicit Self types, applying AST substitutions via the SubstTypes trait, and materializing const generics.
  • Copy-on-write semantics in the DeclEngine ensure 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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →