union

Source   Edit  

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

Procs

proc `$`[T: Union](u: T): string
Stringify u based on its current type in the format type(value). Its main purpose is to enable debugging. Source   Edit  
func `==`[U, V: Union](a: U; b: V): bool {.inline.}

Returns whether the unions a and b are of the same runtime type and value.

Returns false if a and b has no types in common.

Source   Edit  

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 `as`[U: Union](x: U; T: typedesc): untyped

Convert union x to type T. A compile-time error will be raised if T is not a part of the union x.

A runtime defect will be raised if x current type is not T.

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 `of`[U, V: Union](x: U; T: typedesc[V]): bool
Returns whether the union x has a value convertible to union T Source   Edit  
macro `of`[U: Union](x: U; T: typedesc): bool
Returns whether the union x has a value of type T 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 `<-`[T; U: Union](dst: var U; src: T): untyped
Assigns the value src to the union dst, applying conversion as needed. Source   Edit  
template `==`[T: not Union; U: Union](u: U; x: T): untyped

Compares union u with x only if u current type is T.

Returns false if u current type is not T.

Source   Edit  
template `==`[T: not Union; U: Union](x: T; u: U): untyped

Compares union u with x only if u current type is T.

Returns false if u current type is not T.

Source   Edit  
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