# nominal types in webassembly

DevFeed: [nominal types in webassembly](<https://devfeed.tech/articles/nominal-types-in-webassembly-35032.md>)

Original publisher: [Read original article](<https://wingolog.org/archives/2026/03/10/nominal-types-in-webassembly>)

Author: Andy Wingo

Published: 2026-03-10T08:19:34Z

Content type: article

Language: en

Sources: [wingolog](<https://devfeed.tech/sources/wingolog.md>)

Topics: [WebAssembly](<https://devfeed.tech/topics/web-assembly.md>), [wasm](<https://devfeed.tech/topics/wasm.md>)

Tags: [exceptions](<https://devfeed.tech/tags/exceptions.md>), [hoot](<https://devfeed.tech/tags/hoot.md>), [igalia](<https://devfeed.tech/tags/igalia.md>), [isorecursive-types](<https://devfeed.tech/tags/isorecursive-types.md>), [nominal-types](<https://devfeed.tech/tags/nominal-types.md>), [structural-types](<https://devfeed.tech/tags/structural-types.md>), [type-equality](<https://devfeed.tech/tags/type-equality.md>), [types](<https://devfeed.tech/tags/types.md>), [wasm](<https://devfeed.tech/tags/wasm.md>), [wastrel](<https://devfeed.tech/tags/wastrel.md>), [webassembly](<https://devfeed.tech/tags/webassembly.md>)

## AI overview

This article explains WebAssembly's structural type equality, how recursive type groups approximate nominal typing within a module, and how the nominal typing proposal adds nominal types for stronger separation between types across modules.

## Source excerpt

Before the managed data types extension to WebAssembly was incorporated in the standard, there was a huge debate about type equality. The end result is that if you have two types in a Wasm module that look the same, like this: (type $t (struct i32)) (type $u (struct i32)) Then they are for all intents and purposes equivalent. When a Wasm implementation loads up a module, it has to partition the module's types into equivalence classes. When the Wasm program references a given type by name, as in (struct.get $t 0) which would get the first field of type $t, it maps $t to the equivalence class containing $t and $u. See the spec, for more details. This is a form of structural type equality. Sometimes this is what you want. But not always! Sometimes you want nominal types, in which no type declaration is equivalent to any other. WebAssembly doesn't have that, but it has something close: recursive type groups. In fact, the type declarations above are equivalent to these: (rec (type $t (struct i32))) (rec (type $u (struct i32))) Which is to say, each type is in a group containing just itself. One thing that this allows is self-recursion, as in: (type $succ (struct (ref null $succ))) Here the struct's field is itself a reference to a $succ struct, or null (because it's ref null and not just ref). To allow for mutual recursion between types, you put them in the same rec group, instead of each having its own: (rec (type $t (struct i32)) (type $u (struct i32))) Between $t and $u we don't have mutual recursion though, so why bother? Well rec groups have another role, which is that they are the unit of structural type equivalence. In this case, types $t and $u are not in the same equivalence class, because they are part of the same rec group. Again, see the spec. Within a Wasm module, rec gives you an approximation of nominal typing. But what about between modules? Let's imagine that $t carries important capabilities, and you don't want another module to be able to forge those c