This module provides an implementation of anonymous union types (or sum types in many languages) in Nim.
Main features compared to other solutions:
- The union type is unique for a given combination of types. This means union(int | float) in module a is the same as union(float | int) in module b.
There are several limitations at the moment:
- Conversion between a value and a union has to be done via the as operator. There is limited implicit conversion support via the use of the convertible macro.
- The ABI of the union object is unstable due to a lack of a deterministic ordering system. This means a union(T | U) sent as binary from program A might differ from union(T | U) in receiving program B.
- Very limited generics support. This module can only process generics if the generic parameter is resolved to a type at the time of instantiation.
Example:
import union type None = object ## A type for not having any data proc search[T, U](x: T, needle: U): union(U | None) = # Assignment can be done via explicit conversion result = None() as union(U | None) let idx = find(x, needle) if idx >= 0: # Or the `<-` operator which automatically converts the type result <- x[idx] assert [1, 2, 42, 20, 1000].search(10) of None assert [1, 2, 42, 20, 1000].search(42) as int == 42 # For `==`, no explicit conversion is necessary assert [1, 2, 42, 20, 1000].search(42) == 42 # Types that are not active at the moment will simply be treated as not equal assert [1, 2, 42, 20, 1000].search(1) != None() proc `{}`[T](x: seq[T], idx: Natural): union(T | None) = ## An array accessor for seq[T] but doesn't raise if the index is not there # Using makeUnion, an expression may return more than one type makeUnion: if idx in 0 ..< x.len: x[idx] else: None() assert @[1]{2} of None assert @[42]{0} == 42 import json # With unpack(), dispatching based on the union type at runtime is possible! var x = 42 as union(int | string) block: let j = unpack(x): # The unpacked variable name is `it` by default %it assert j.kind == JInt x <- "string" block: let j = # You can give the unpacked variable a different name via the second # parameter, too. unpack(x, upk): %upk assert j.kind == JString
Macros
macro `as`(x: typed; U: typedesc[Union]): untyped
- Convert x into union type U. A compile-time error will be raised if x is not a type within U. Source Edit
macro `as`[U, V: Union](x: U; T: typedesc[V]): untyped
-
Convert union x to union T.
If x doesn't have any type in common with T, a compile-time error will be raised. Otherwise, x will be converted to T if x current type is one of T types.
A runtime defect will be raised if x current type is not one of T types.
Source Edit macro convertible(T: typedesc[Union]): untyped
- Produce converters to convert to/from union type T from/to its inner types implicitly. Source Edit
macro makeUnion(expr: untyped): untyped
-
Produce an union from expression expr. The expression may return multiple different types, of which will be combined into one union type.
The expression must return more than one type. A compile-time error will be raised if the expression returns only one type.
Due to compiler limitations, this macro cannot evaluate macros within expr and might miss a few expressions. In those cases, the expressions need to be analyzed can be tagged by making a call to unionTail, which is introduced into expr scope.
Example:
let x = makeUnion: if true: 10 else: "string" assert x is union(int | string)
Source Edit macro unpack[T: Union](u: T; body: untyped): untyped
-
Unpack the union u into the variable it with its current type at runtime, then run body with it exposed.
This is an overload of unpack(T, untyped, untyped) <#union,T,untyped,untyped>_.
Example:
var u = 42 as union(int | string | float) unpack(u): # `it` will be available as a form of `int | string | float` generic. # # This means the type can be dispatched at compile-time or the parameter # can be fed into a generic! when it is int: assert(it == 42) elif it is string: assert(it == "str") else: discard
Source Edit macro unpack[T: Union](u: T; ident, body: untyped): untyped
-
Unpack the union u into a variable with its current type at runtime under the name specified in ident, then run body with variable ident exposed.
Example:
var u = 42 as union(int | string | float) unpack(u, unpacked): # `unpacked` will be available as a form of `int | string | float` # generic. # # This means the type can be dispatched at compile-time or the parmeter # can be fed into a generic! when unpacked is int: assert(unpacked == 42) elif unpacked is string: assert(unpacked == "str") else: discard
Source Edit
Templates
template union(T: untyped): untyped
-
Returns the union type corresponding to the given typeclass. The typeclass must not contain built-in typeclasses such as proc, ref, object,...
The typeclass may contain other typeclasses, or other unions.
If the typeclass contain one unique type, then that unique type will be returned.
Source Edit