Skip to content

rustc hangs when compiling dyn-encoded polymorphic recursion #156272

Description

@naganohara-yoshino

Summary

rustc hangs indefinitely instead of emitting a recursion limit or overflow error when compiling a generic function that uses polymorphic recursion over a non-regular recursive type.

By hiding the non-regular recursion behind a dyn Fn thunk, the compiler's drop-check overflow checks are bypassed. The generic function then triggers an infinite sequence of distinct monomorphizations, causing rustc to hang without termination or diagnostics.

Code

#![allow(dead_code)]

use std::fmt::Debug;

enum PerfectPrime<A> {
    Fst(A),
    Snd(Box<dyn Fn() -> PerfectPrime<(A, A)>>),
}

fn myprint<A: Debug>(p: PerfectPrime<A>) {
    match p {
        PerfectPrime::Fst(a) => println!("{:?}", a),
        PerfectPrime::Snd(f) => myprint(f()),
    }
}

fn main() {
    let p0: PerfectPrime<i32> = PerfectPrime::Fst(42);
    myprint(p0);
}

Expected behavior

rustc should terminate and emit an error, such as an infinite monomorphization diagnostic.

Actual behavior

rustc hangs indefinitely during compilation. In local testing, the compilation did not finish after running for over 1 hour, and the compiler output remained stuck on:

Building [                             ] 0/1: a(bin)

No diagnostic, error, or panic is emitted.

Analysis

This issue exposes a fundamental conflict between non-regular recursive types (where the type parameter changes in the recursive definition, e.g., A to (A, A)) and monomorphization.

1. The Language Design Conflict

In languages like OCaml and Haskell, generics are implemented using uniform representation (type erasure or boxing). A polymorphic function is compiled exactly once and operates on pointers. Consequently, polymorphic recursion over a non-regular type is perfectly valid.

For example, the equivalent non-regular type and polymorphic recursion in Haskell compiles and runs perfectly (laziness makes the explicit thunk unnecessary):

data Perfect a = Fst a | Snd (Perfect (a, a))

myPrint :: Show a => Perfect a -> IO ()
myPrint (Fst a) = print a
myPrint (Snd p) = myPrint p

main :: IO ()
main = do
  let p0 = Snd (Fst (1, 2))
  myPrint p0

Similarly, in OCaml, the exact thunked pattern is standard and accepted:

type 'a perfect_prime =
  | Fst of 'a
  | Snd of (unit -> ('a * 'a) perfect_prime)

Rust, however, uses monomorphization. Every time a generic function is called with a new type, the compiler generates a specialized copy of that function. Thus, non-regular polymorphic recursion is fundamentally incompatible with Rust's compilation model because it requires generating infinitely many distinct function bodies.

2. Bypassing Type-Level Limits

Normally, Rust catches this non-regular recursion during instantiation. Simply defining a directly recursive non-regular type is allowed, but instantiating it triggers a drop-check overflow:

enum Perfect<A> {
    Fst(A),
    Snd(Box<Perfect<(A, A)>>),
}

fn main() {
    let p0: Perfect<i32> = Perfect::Fst(42);
}

This fails correctly with:

error[E0320]: overflow while adding drop-check rules for `Perfect<i32>`

However, introducing the dyn Fn thunk in PerfectPrime successfully hides the infinitely expanding type behind a dynamically dispatched trait object. Because dyn Fn breaks the visible structural recursion for the drop-checker, instantiation succeeds, and the type definition bypasses structural limits.

3. Runaway Monomorphization

Because instantiation is accepted, the non-regular recursion reappears at the function level. In the Snd branch of myprint, f() returns PerfectPrime<(A, A)>. Compiling myprint::<A> forces the compiler to monomorphize myprint::<(A, A)>, which requires myprint::<((A, A), (A, A))>, and so on ad infinitum:

myprint::<A>
myprint::<(A, A)>
myprint::<((A, A), (A, A))>
myprint::<(((A, A), (A, A)), ((A, A), (A, A)))>
...

rustc continuously attempts to generate these larger instantiations but fails to trigger standard monomorphization recursion limits (like #![recursion_limit]), resulting in a silent hang.

Meta

rustc --version --verbose:

rustc 1.97.0-nightly (e95e73209 2026-05-05)
binary: rustc
commit-hash: e95e73209faf6ead2bc5c7636e45e589a751b79b
commit-date: 2026-05-05
host: x86_64-pc-windows-msvc
release: 1.97.0-nightly
LLVM version: 22.1.4

(No backtrace is available because compilation does not panic or complete).

Metadata

Metadata

Labels

A-dyn-traitArea: trait objects, vtable layoutC-bugCategory: This is a bug.I-hangIssue: The compiler never terminates, due to infinite loops, deadlock, livelock, etc.I-monomorphizationIssue: An error at monomorphization time.T-compilerRelevant to the compiler team, which will review and decide on the PR/issue.

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions