Casting
Paradox uses the as operator to convert values between compatible types. The casting system is built around a wrap graph — a bidirectional graph of wrap type relationships that the compiler uses to find the shortest conversion path between any two types.
The as Operator
Cast a value to a compatible type with as:
age as Text
count as Double
flag as Integer
as has the lowest operator precedence, so it binds last:
x + 1 as Text // equivalent to: (x + 1) as Text
Primitive Casts
Certain primitive-to-primitive casts are built in:
| From | To |
|---|---|
Any primitive |
|
|
|
|
|
|
|
|
|
|
|
|
|
These are direct conversions that don’t go through the wrap graph.
Collection Casts
Arrays and sets with the same element type are interchangeable:
myArray as {Text} // [Text] -> {Text}
mySet as [Integer] // {Integer} -> [Integer]
Collection casts deduplicate when converting to a set, and produce an unspecified order when converting from a set to an array.
Wrap Casts
Wrap types form the core of the casting system. Each wrap declaration creates a bidirectional edge in the wrap graph:
wrap Natural: Integer
wrap Positive: Natural
wrap Port: Natural
This produces a graph:
Integer <-> Natural <-> Positive
<-> Port
The compiler uses shortest-path search (Dijkstra’s algorithm) to find a cast route between any two types connected in this graph. The search prefers the shortest distance, favouring zero-cost operations (data access like unwrap) over computational conversions (like primitive casts). This means a path through wrap/unwrap edges — which are free at runtime in backends that support it — will always be chosen over a longer path that involves computation.
myPositive as Integer // Positive -> Natural -> Integer (2 unwraps, zero-cost)
myPositive as Port // Positive -> Natural -> Port (1 unwrap + 1 wrap, zero-cost)
myNatural as Port // Natural -> Port (1 wrap, zero-cost)
myPort as Integer // Port -> Natural -> Integer (2 unwraps, zero-cost)
Each step in the path is either a wrap (constructing a wrapper) or an unwrap (extracting the inner value). Both are data access operations — zero-cost where the backend supports it. The compiler only falls back to computational primitive casts when no wrap-only path exists.
Unwrap-then-Primitive Casts
When a wrap type bottoms out at a primitive, the compiler can chain an unwrap path with a primitive cast:
wrap Name: Text
wrap UserId: Integer
myUserId as Text // UserId -> Integer -> Text (unwrap + primitive cast)
myName as Boolean // Name -> Text -> Boolean (unwrap + primitive cast)
The compiler first unwraps to the innermost primitive, then applies a built-in primitive cast if one exists.
Cast Errors
When no path exists between two types, the compiler reports the unwrap chains of both types to help diagnose the problem:
Cannot cast from Foo to Bar automatically Foo unwraps to: Foo -> Integer Bar unwraps to: Bar -> Text
Identity Casts
Casting a value to its own type is always valid and is a no-op:
x as Integer // no-op when x is already Integer
See Also
-
Wrap Types — Defining wrap types and using
unwrap -
Type Casting in Expressions — The
asoperator in context -
Validation — Using
asinside validation blocks