Verse Wiki — the Verse handbook for Blueprint authors
Extension · EXTRA

subtype, castable, and Why Types Can't Be Compared

Verse lets you store a "type itself" in a variable as a value — which feels like a cheat code, right up until you try to use one as a map key: the compiler coolly replies not comparable, and Epic marks the ticket won't fix. Behind that wall stand the theoretical roots of Verse's type system — and one mathematical theorem.

1. Types as Values: The subtype Family

In the main lesson we stored instances in variables; the advanced play is storing a type itself in a variable — a bit like keeping a Class Reference variable in Blueprints: what's inside is not some particular instance but "which kind of Blueprint to use". Verse provides a family of spellings for "some subtype of a type" (a subtype is roughly its Child Blueprint): subtype(t) is "any subtype of t"; castable_subtype(t) is "a subtype that can be a Cast target"; concrete_subtype(t) is "a subtype you can actually build instances of". They can appear as the type of fields, variables and parameters — so "which kind of loot to spawn" can be configured with a field that holds a type:

loot_spawner.verse
using { /UnrealEngine.com/Temporary/Diagnostics }

loot := class:
    Value:int = 0

coin := class(loot):
    Value<override>:int = 10

gem := class(loot):
    Value<override>:int = 100

# The field stores not an instance but the type itself
loot_spawner := class:
    LootType:concrete_subtype(loot) = coin

    # Instantiate straight from the stored type (a forum-verified pattern)
    MakeOne():loot =
        LootType{}

Reading the code: this builds a base class loot and its two child Blueprints coin and gem (each overriding Value to 10 and 100). The key is loot_spawner: its field LootType holds not an instance but "which kind of loot", filled in here as coin. MakeOne() then builds an instance straight from that stored type (LootType{}) — the equivalent of Spawning "a class specified by a variable" in Blueprints.

Swap that line for loot_spawner{LootType := gem} and the same spawner starts spitting out gems — no extra child Blueprint, no pile of Branch nodes: "which type to use" has itself become configurable data. Combined with the Cast from the main lesson, you can also inspect in reverse: if (G := gem[Something]): is "Cast To gem", checking whether a loot really is a gem. (Aside: for an interface to be a valid runtime Cast target involves the <castable> marker — see the Verse API Reference for the details.)

2. Hitting the Wall: Types Are Not Comparable

Once you can store them, the natural next step is a "type → handler" lookup table: meet a coin, go this way; meet a gem, go that way; register the lot in a Map (locker) — so elegant. The forum thread "[Major] Verse types are not comparable" tried it for all of us — road closed:

type_map_fail.verse
# ✗ Compile error:
# 'subtype(loot)' cannot be used as the type of map keys
# because it is not comparable for equality
# var Handlers:[subtype(loot)]int = map{}

That commented-out line above is the attempt to use a type as a Map key — compilation refuses outright. And it's not just keys: two type values don't even get a single = (equality test) between them. That is, you can pass types around, build instances from them, do Casts with them — yet you can never ask "are these two types the same one". Lesson 17 covered how a Map's keys must be types that "support equality"; types themselves are precisely not on the list, and Epic has stated plainly: it will not be fixed.

3. Epic's Explanation: Types as Functions, and Rice's Theorem

Why so firm? Epic engineer Saam gave a theory-level answer: in Verse, a "type" is essentially a piece of judgment — think of it as a little program you can hand any value to and ask "do you count as one of these?", getting a yes or a no. That design makes the type tricks wonderfully flexible (the subtype spellings above are one product), but it also slams into a mathematical dead end: deciding "these two types are equal" amounts to deciding "do these two judgment programs always give the same result"; and Rice's theorem from computability theory says that any meaningful question about "what a program will do when it runs" is impossible to decide by algorithm — no universal algorithm gets every case right.

So "types can't be compared" is not Epic being lazy; it is mathematics drawing the line: better to give no = at all than one bound to fail or hang in some cases. The ticket is marked won't fix for exactly that reason. It is also a rare window: Verse positions itself as a "functional logic language", where types, functions and propositions are one family underneath — the "whether a wire goes through is itself control flow" you experienced in Lesson 12 and the "a type is really a piece of judgment" here spring from the same source.

4. Workarounds: Three Stand-ins for the Lookup-Table Dream

▢ Stand-in 1: hang a "name tag" on the type. The key can't be a type, but it can be a string or an enum (enumeration asset): give each Blueprint a fixed field like Kind:string and use that name tag as the Map key — the lookup runs just fine, at the cost of guaranteeing yourself that no two tags collide.

▢ Stand-in 2: dispatch by Casting one by one. Store the types with castable_subtype(t), then try Casts in order with if (C := some_type[Ref]): — essentially trading the "lookup" for a string of Branches, perfectly adequate while the types are few.

▢ Stand-in 3: interface polymorphism (most recommended). Back on Lesson 21's home turf: instead of "look up a handler by type", write each one's handling logic into its own Blueprint, have them all sign one interface (BPI), and leave the dispatching to runtime polymorphism. Most "type → behavior" lookup cravings are really the signal that "the interface hasn't been extracted yet".

Why can't a value that "holds a type" (subtype(...)) be used as a Map key?

Sources & Further Reading

Compiled from the Epic developer forums and official documentation:

Forum: [Major] Verse types are not comparable (with Epic engineer Saam's reply) ↗

Official docs: Comparable in Verse ↗

Official docs: Verse Glossary (type cast entry) ↗