LogoChemical Docs
Ctrl+K

Variants

Variants (also known as Tagged Unions) allow a variable to hold one of several different types of data.

Definition and Layout

A variant consists of a Tag (automatically managed integer) and a Union of the data associated with each case.

variant Shape {

    Circle(radius : float)

    Rect(w : float, h : float)

    None()

}

Pattern Matching

Pattern matching is strictly reserved for Variants.

if (var ...) Syntax

Extraction and condition in one step.

if (var Shape.Circle(r) = my_shape) {

    printf("Radius is %f\n", r)

}

match / switch Syntax

switch (my_shape) {

    Shape.Circle(r) => return 3.14 * r * r

    Shape.Rect(w, h) => return w * h

    default => return 0.0

}

The else Unwrapping

Used for quick data extraction. If the variant doesn't match the case, the else block is executed (must return, panic, or skip).

var Shape.Circle(r) = my_shape else unreachable

printf("Radius: %f\n", r);

Type Testing with is

Check the state without extracting data.

if (my_shape is Shape.Circle) {

    // ...

}

IMPORTANT
Because Variants use a tag and union layout, they are highly memory-efficient, consuming only as much space as the largest case + the tag.

Methods in Variants

Variants can have methods that operate on their internal data:

variant FuncInVariant {

    First(value : int)

    Second(value : int)

    

    func get(&self) : int {

        switch(self) {

            First(value) => return value;

            Second(value) => return value;

        }

    }

}



var v = FuncInVariant.First(232)

v.get()  // Returns 232

Variant Inheritance from Structs

Variants can inherit from structs to share common data across all cases:

struct Point {

    var a : int = 10

    var b : int = 20

    

    func multiply(&self) : int {

        return a * b

    }

}



variant Shape : Point {

    Circle(radius : int)

    Rect(width : int, height : int)

}



var s = Shape.Circle(5)

s.a  // 10 (inherited, default initialized)

s.b  // 20 (inherited, default initialized)

s.multiply()  // 200

Variant Interface Implementation

Variants can implement interfaces with @override:

interface Giver {

    func give(&self) : int

}



variant OptionalInt : Giver {

    None()

    Some(value : int)

    

    @override

    func give(&self) : int {

        switch(self) {

            None() => return -1;

            Some(value) => return value;

        }

    }

}

Static Dispatch with Variants

Use generic constraints for zero-cost abstraction:

func <T : Giver> get_value(v : &T) : int {

    return v.give()

}



var opt = OptionalInt.Some(42)

get_value(opt)  // 42

Dynamic Dispatch with Variants

Use dyn for runtime polymorphism:

func process_giver(g : dyn Giver) : int {

    return g.give()

}



var opt = OptionalInt.Some(93)

process_giver(dyn<Giver>(opt))  // 93