How Trait Coherence Checking Works in Sway's Type System: Orphan and Overlap Rules Explained

Trait coherence checking in Sway enforces the orphan rule and overlap rule during semantic analysis to guarantee that trait implementations are unambiguous and deterministic across packages.

Sway's type system guarantees trait coherence through a rigorous two-phase validation process that runs during semantic analysis in the FuelLabs/sway compiler. This mechanism ensures that no two implementations of a trait can conflict for the same type, and that implementations respect package boundaries to prevent downstream crates from hijacking foreign traits. The checks are orchestrated through sway-core/src/semantic_analysis/namespace/trait_coherence.rs and trait_map.rs, leveraging the compiler's Engines and Handler infrastructure.

The Two Phases of Trait Coherence Checking in Sway

Sway validates trait implementations in two distinct phases that operate on the TraitMap after all module items have been collected.

Phase 1: Orphan Rule Validation

The orphan rule prevents implementations where neither the trait nor any of the types being implemented are defined in the current package. This stops downstream crates from writing impl ForeignTrait for ForeignType, which would create ambiguity if multiple crates attempted the same implementation.

Phase 2: Overlap Detection

The overlap rule guarantees that for any concrete type-trait pair, at most one implementation applies. This is enforced by attempting to unify the type signatures of every pair of implementations; if two heads can unify and their trait bounds are satisfied, the compiler rejects the program.

Orphan Rule Implementation in trait_coherence.rs

The orphan check is driven by check_orphan_rules_for_impls, which traverses every lexical scope in the root module and delegates to check_orphan_rules_for_impls_in_scope.

// sway-core/src/semantic_analysis/namespace/trait_coherence.rs
pub(crate) fn check_orphan_rules_for_impls(
    handler: &Handler,
    engines: &Engines,
    current_package: &Package,
) -> Result<(), ErrorEmitted> {
    module.walk_scope_chain(|lexical_scope| {
        let trait_map = &lexical_scope.items.implemented_traits;
        check_orphan_rules_for_impls_in_scope(handler, engines, current_package, trait_map)
    });
}

Inside check_orphan_rules_for_impls_in_scope, the compiler performs four critical steps:

  1. Skips contract types—they are exempt from coherence checking.
  2. Filters by package—impls originating from external packages are ignored.
  3. Checks trait origin—if the trait is defined in the current package, the impl is automatically valid.
  4. Scans for local types—via references_local_type, the compiler walks the impl's type arguments, generic parameters, and self-type to detect any reference to a local enum, struct, or array.

The references_local_type function uses type_id.walk_inner_types to recursively inspect types:

fn references_local_type(
    engines: &Engines,
    current_package: &Package,
    type_id: TypeId,
) -> bool {
    let found_local = Cell::new(false);
    type_id.walk_inner_types(
        engines,
        IncludeSelf::Yes,
        &|inner_type_id| {
            let inner_type = engines.te().get(*inner_type_id);
            let is_local = match *inner_type {
                TypeInfo::Enum(decl_id) => {
                    let enum_decl = engines.de().get_enum(&decl_id);
                    is_from_local_package(current_package, &enum_decl.call_path)
                }
                TypeInfo::Struct(decl_id) => {
                    let struct_decl = engines.de().get_struct(&decl_id);
                    is_from_local_package(current_package, &struct_decl.call_path)
                }
                TypeInfo::Array(_, _) | TypeInfo::StringArray(_) => true,
                _ => false,
            };
            if is_local { found_local.set(true); }
        },
        &|trait_constraint| {
            if is_from_local_package(current_package, &trait_constraint.trait_name) {
                found_local.set(true);
            }
        },
    );
    found_local.get()
}

If no local type is found, the compiler emits CompileError::IncoherentImplDueToOrphanRule.

Overlap Checking in trait_map.rs

Once orphan rules pass, the compiler validates that no two implementations overlap using check_impls_for_overlap in sway-core/src/semantic_analysis/namespace/trait_map.rs.

The algorithm builds a map of trait-to-concrete-types for fast lookup, then compares every pair of implementations:

  1. Unification test: Uses UnifyCheck::constraint_subset to determine if the two impl heads can be made identical through type substitution.
  2. Reference handling: Custom is_unified_type_subset logic ensures that &T does not unify with &mut T for impl selection.
  3. Constraint validation: Collects trait constraints (self_tcs and other_tcs) from generic parameters and verifies they are satisfied by the concrete types in the trait map.

If two impls unify and all constraints are satisfied, the compiler reports:

handler.emit_err(CompileError::ConflictingImplsForTraitAndType {
    trait_name: engines.help_out(self_entry.inner.key.name.as_ref()).to_string(),
    type_implementing_for: engines.help_out(self_entry.inner.key.type_id).to_string(),
    type_implementing_for_unaliased: engines.help_out(self_entry.inner.key.type_id).to_string(),
    existing_impl_span: self_entry.inner.value.impl_span.clone(),
    second_impl_span: other_entry.span.clone(),
});

If no overlap is detected, the trait maps are merged via trait_map.extend(other, engines).

Code Examples: Valid and Invalid Impls

✅ Valid Coherent Implementation

This implementation passes both orphan and overlap checks because the trait and the type are both defined in the current package.

library;

// Trait defined in this package.
trait Foo<T> {
    fn foo(self, x: T) -> T;
}

// Local struct – the impl references a local type.
struct Bar<T> {
    inner: T,
}

// Implementation is permitted because the trait is local *and* the self‑type
// (`Bar<T>`) is also local.
impl<T> Foo<T> for Bar<T> {
    fn foo(self, x: T) -> T {
        x
    }
}

❌ Orphan Rule Violation

This implementation fails because it implements a foreign trait for a foreign type without referencing any local types.

// External trait `External::Baz` lives in a different package.
use external::Baz;

// No local type appears in the impl; only a generic parameter.
impl<T> Baz<T> for T {
    fn baz(self) -> T { self }
}

Compiler output:


error: incoherent impl due to orphan rule
  --> src/main.sw:8:1
   |
8  | impl<T> Baz<T> for T {
   | ^^^^^^^^^^^^^^^^^^^^^
   |
   = note: the trait `external::Baz` is defined in crate `external`
   = note: the type `T` is not a local type

❌ Overlap Rule Violation

These implementations conflict because the generic impl<T> MyTrait for T overlaps with the concrete implementations for S and T.

trait MyTrait {}
struct S;
struct T;

// Both impls are valid individually.
impl MyTrait for S {}
impl MyTrait for T {}

// A generic impl that overlaps with the concrete ones.
impl<T> MyTrait for T {}

Compiler output:


error: conflicting implementations of trait `MyTrait` for type `T`
  --> src/main.sw:9:1
   |
5  | impl MyTrait for S {}
   | ------------------- first impl
9  | impl<T> MyTrait for T {}
   | ^^^^^^^^^^^^^^^^^^^^ second impl
   = note: the impls overlap because `T` can be instantiated as `S` or `T`

Summary

  • Two-phase validation: Sway checks trait coherence through orphan-rule validation followed by overlap detection during semantic analysis.
  • Orphan rule enforcement: The check_orphan_rules_for_impls function in trait_coherence.rs ensures every implementation references either a local trait or a local type, preventing downstream crates from hijacking foreign trait-type pairs.
  • Overlap prevention: The check_impls_for_overlap function in trait_map.rs uses unification (UnifyCheck::constraint_subset) to verify that no two implementations can apply to the same concrete type, ensuring deterministic method resolution.
  • Error specificity: Violations emit precise compiler errors (IncoherentImplDueToOrphanRule or ConflictingImplsForTraitAndType) with source spans to guide developers toward coherent designs.

Frequently Asked Questions

What is the orphan rule in Sway's type system?

The orphan rule is a coherence constraint that prevents implementations where neither the trait nor any of the types involved are defined in the current package. According to the implementation in trait_coherence.rs, an implementation passes the orphan check only if the trait is defined locally or the implementation references at least one local type (enum, struct, or array). This prevents downstream crates from writing impl ForeignTrait for ForeignType, which would create ambiguity if multiple crates attempted the same implementation.

How does Sway detect overlapping trait implementations?

Sway detects overlapping implementations through the check_impls_for_overlap function in trait_map.rs. The compiler builds a map of trait-to-concrete-types and compares every pair of implementations using unification (UnifyCheck::constraint_subset). If two implementation heads can be unified (made identical through type substitution) and their trait bounds are satisfied, the compiler emits CompileError::ConflictingImplsForTraitAndType. This ensures that for any concrete type-trait pair, exactly one implementation applies, enabling deterministic method resolution.

Why does Sway reject generic implementations like impl<T> Trait for T?

Sway rejects blanket implementations like impl<T> Trait for T when they overlap with existing concrete implementations (such as impl Trait for MyStruct) because this violates the overlap rule. During coherence checking, the compiler unifies the generic T with concrete types like MyStruct, determining that both implementations could apply to the same type. Without rejecting this overlap, method dispatch would be ambiguous. The compiler reports this as ConflictingImplsForTraitAndType, requiring developers to either remove the concrete implementations or constrain the generic implementation with trait bounds that exclude the concrete types.

Where in the Sway compiler does trait coherence checking occur?

Trait coherence checking occurs during semantic analysis in the sway-core crate, specifically within the namespace resolution phase. The orphan rule check is implemented in sway-core/src/semantic_analysis/namespace/trait_coherence.rs via check_orphan_rules_for_impls, while the overlap check resides in sway-core/src/semantic_analysis/namespace/trait_map.rs within check_impls_for_overlap. Both checks are invoked after the module's items have been collected into a TraitMap, ensuring that all implementations are validated before method selection occurs.

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 →