LogoChemical Docs
Ctrl+K

Generics

Chemical provides a powerful generics system that allows you to write reusable, type-safe code. Generics can be applied to functions, structs, variants, and interfaces.

Basic Syntax

Generic parameters are specified within angle brackets < > before the function name or after the type name.

struct Box<T> {

    var item : T

}



var int_box = Box<int> { item : 42 }

Generic Functions

Generic functions can have multiple type parameters and work with any type:

func <T, K, R> sum(a : T, b : K) : R {

    return (a + b) as R

}



// Usage - types are inferred from arguments

var result = sum(10, 20)  // Returns 30



// Explicit type parameters when needed

var long_result = sum<long, long, long>(20, 30)

Generic Structs

Structs can have multiple generic type parameters:

struct PairGen<T, U, V> {

    var a : T

    var b : U

    

    func add(&self) : V {

        return (a + b) as V

    }

}



var p = PairGen<int, int, int> { a : 10, b : 12 }

var sum = p.add()  // Returns 22

Generic Methods on Structs

Generic structs can contain methods, and methods themselves can introduce additional generic parameters:

struct GenericStruct<T, U> {

    var value1 : T

    var value2 : U

    

    func <V> generic_method(param : V) : V {

        return param

    }

}

Storing Generic Structs

Generic structs can be stored in other structs:

@direct_init

struct Container {

    var pair : PairGen<short, short, short>

    

    @make

    func make() {

        return {

            pair : PairGen<short, short, short> { a : 33, b : 10 }

        }

    }

}

Default Type Parameters

You can provide default values for generic type parameters:

// R defaults to int if not specified

func <T, R = int> convert(value : T) : R {

    return value as R

}



// Struct with default type parameter

struct GenStructDef<T = long> {

    var value : T

    

    func test(&self) : bool {

        return T is long

    }

}



// Using defaults (empty angle brackets)

var s = GenStructDef<> { value : 10 }

// Equivalent to GenStructDef<long> { value : 10 }

When a function argument provides enough information, the inferred type takes precedence over the default:

func <T = short> give_me_size() : T {

    if(T is short) { return 2 } else if(T is int) { return 4 } else { return 8 }

}



// If result is assigned to an int, T is inferred as int (not default short)

var result : int = give_me_size()  // Returns 4

Generic Dispatch & Constraints

You can constrain a generic type to only those that implement a specific interface.

interface Printable {

    func print(&self)

}



func <T : Printable> print_item(item : &T) {

    item.print()

}

Conditional Compilation with is

The if (T is Type) construct allows the compiler to choose different code paths based on the concrete type provided for a generic parameter. This is similar to C++ if constexpr.

func <T> get_type_size() : int {

    if (T is short) {

        return 2

    } else if (T is int) {

        return 4

    } else if (T is long) {

        return 8

    } else {

        return sizeof(T) as int

    }

}

Multi-Type Checks

You can combine multiple type checks:

func <T> gen_ret_func(value : T) : T {

    if(T is char || T is uchar || T is i8 || T is u8) {

        return value + 1

    } else if(T is short || T is ushort || T is i16 || T is u16) {

        return value + 2

    } else if(T is int || T is uint || T is i32 || T is u32) {

        return value + 4

    } else if(T is longlong || T is ulonglong || T is i64 || T is u64) {

        return value + 8

    } else {

        return value + 0

    }

}

Inside Generic Structs

Type checks work inside generic struct methods:

struct TypeChecker<T> {

    func get_integer() : T {

        if(T is char || T is uchar) { 

            return 1 

        } else if(T is short || T is ushort) { 

            return 2 

        } else if(T is int || T is uint) { 

            return 4 

        } else if(T is bigint || T is ubigint) { 

            return 8 

        } else { 

            return 0 

        }

    }

}

Generic Variants

Generics are essential for creating flexible data structures like Option or Result.

variant Option<T> {

    Some(value : T)

    None()

}



var opt = Option.Some<int>(10)



// Multi-parameter generic variant

variant Result<T, E> {

    Ok(value : T)

    Err(error : E)

}

Pattern Matching with Generic Variants

func get_value(opt : Option<int>) : int {

    switch(opt) {

        Some(value) => return value

        None => return -1

    }

}

Generic Interfaces

Interfaces can be generic and use the Self keyword:

interface GenAddInterface<Output, Rhs = Self> {

    func add(&self, rhs : Rhs) : Output

}



struct Point : GenAddInterface<Point, Point> {

    var a : int

    var b : int

    

    @override

    func add(&self, rhs : Point) : Point {

        return Point {

            a : a + rhs.a,

            b : b + rhs.b

        }

    }

}

Extension Functions on Generic Types

You can define extension functions for generic structs:

func <T, U, V> (pair : &PairGen<T, U, V>) ext_div() : V {

    return pair.a / pair.b

}



// Usage

var p = PairGen<short, short, short> { a : 56, b : 7 }

var result = p.ext_div<short, short, short>()  // Returns 8

Generics in Namespaces

Generic types work within namespaces:

namespace gen_container {

    struct ContainedGen<T> {

        var x : T

        

        func size(&self) : int {

            if(T is short) { return 2 }

            else if(T is int) { return 4 }

            else if(T is bigint) { return 8 }

            else { return 0 }

        }

    }

}



// Access via namespace

var x = gen_container::ContainedGen<int> { x : 10 }

Comptime Functions in Generic Contexts

Comptime functions called within generic contexts are re-evaluated for each type instantiation:

comptime func <T> comptime_func() : int {

    if(T is short) { return 2 }

    else if(T is int) { return 4 }

    else if(T is bigint) { return 8 }

    else { return 0 }

}



func <T> gen_wrap_comptime() : int {

    return comptime_func<T>()

}



// Each call produces different results

gen_wrap_comptime<short>()   // Returns 2

gen_wrap_comptime<int>()     // Returns 4

gen_wrap_comptime<bigint>()  // Returns 8

Lambda Functions in Generic Containers

Lambdas can exist inside generic functions and use the generic type:

func <T> gen_lamb_func() : T {

    var lamb : () => T = () => {

        return 287

    }

    return lamb()

}



func <T> gen_lamb_with_param() : T {

    var lamb : (a : T) => T = (a : T) => {

        return a

    }

    return lamb(93837)

}