Debug.crash aborts the Elm runtime despite the no-runtime-exceptions guarantee
0 reputation · 27 May 2021, 10:14 UTC
0 reputation · 27 May 2021, 10:14 UTC
Elm guarantees that pure code cannot throw runtime exceptions, yet Debug.crash is typed as Never and throws a JavaScript exception whenever it is evaluated. This leaves an unresolved gap between the type-level guarantee and observable behavior in the Elm compiler.
The goal is to determine whether a reachable Debug.crash call should be treated as a user-code defect, a compiler warning candidate, or an acceptable development-only escape hatch. Because Never permits the call in any position, static analysis alone does not flag reachable crash sites before deployment.
Is Debug.crash intended to remain reachable in production applications, and what verification confirms that no code path can evaluate it? Should the compiler treat statically reachable Debug.crash as an error rather than a runtime abort?
35775 reputation · 27 May 2021, 15:11 UTC
Debug.crash is typed as Never in Elm's core library. This type signature is intentional: it allows Debug.crash to appear in any context where a value is expected, including unreachable positions like the right side of a case that can never be matched. At runtime, evaluating Debug.crash throws a JavaScript Error, which terminates the Elm runtime.
The apparent contradiction stems from what Never actually guarantees. The type Never does not mean "this code is unreachable at runtime." It means "if this code is ever evaluated, the program should not continue normally." The compiler uses Never to prove to itself that certain branches are impossible, but it cannot statically prove that a Debug.crash call will never be reached in a reachable code path.
In other words, Never is a type-level promise about control flow, not a runtime guarantee. A reachable Debug.crash call indicates programmer error—either an incomplete pattern match, a logic bug, or a debug statement left in production code.
elm make on your project. Elm does not currently emit a warning for statically reachable Debug.crash calls, which is a known gap.Debug.crash is reachable, the browser console will show an uncaught exception and the Elm runtime will halt.Debug.crash in production modules. Any occurrence that is not in an explicitly unreachable branch (e.g., the Nothing case of a Maybe that is always Just by construction) is a candidate for removal or replacement with proper error handling.Do not rely on Debug.crash for production error handling. It is designed as a development tool to catch logic errors early. If you need to handle errors in production, use Result, Maybe, or custom error types. Replace any reachable Debug.crash calls with explicit error values that the type system can track.
If you are seeing Debug.crash in a position where it "should" be unreachable (e.g., the Nothing case of a Maybe), please share the specific code pattern. Depending on the context, there may be a way to narrow the type or restructure the code to make the compiler accept the proof of irreachability.
Use comments to ask for clarification. Post a solution as an answer.
35,775 reputation · 27 May 2021, 12:58 UTC
Elm's "no runtime exceptions" guarantee covers pure evaluation; Debug.crash is deliberately outside that contract because its type Never marks unreachable code rather than a value that can be produced. A reachable call is therefore a user-code defect, not a compiler bug, but the compiler does not currently reject statically reachable Debug.crash sites.
Practical verification: build with elm make --optimize and search the emitted JavaScript for Debug.crash throw sites; any hit means a code path can evaluate it. Custom lint rules can flag these before deployment, but they must ignore unreachable branches eliminated by dead-code elimination.