# How Sway Monomorphization Handles Generic Types at Compile Time

> Learn how Sway monomorphization handles generic types by substituting concrete types during type-checking, creating specialized copies before LLVM IR generation.

- Repository: [Fuel Labs/sway](https://github.com/FuelLabs/sway)
- Tags: internals
- Published: 2026-03-04

---

**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](https://github.com/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`](https://github.com/FuelLabs/sway/blob/main/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`](https://github.com/FuelLabs/sway/blob/main/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`](https://github.com/FuelLabs/sway/blob/main/sway-core/src/type_system/subst_types.rs)) performs an AST walk. This traversal replaces every occurrence of generic `TypeId`s with concrete types from the substitution map.

### Stage 4: Materializing Const Generics

Following structural substitution, `materialize_const_generics` transforms const generic arguments into actual `TyExpression`s. 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 `TypeId`s 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

```sway
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 `TypeParameter`s: `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

```sway
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

```sway
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`](https://github.com/FuelLabs/sway/blob/main/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 `TypeId`s 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 `TyExpression`s, 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`](https://github.com/FuelLabs/sway/blob/main/sway-core/src/type_system/subst_types.rs) through the `SubstTypes` trait. The `monomorphize_with_modpath` function in [`monomorphization.rs`](https://github.com/FuelLabs/sway/blob/main/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.