Generics

Generics

Sun supports generic classes and functions, parameterized by type. This allows you to write reusable code that works with different types while maintaining type safety.

Generic Functions

Generic functions are declared with type parameters in angle brackets after the function name:

function identity<T>(x: T) T {
    return x;
}
 
function main() i32 {
    var a = identity<i32>(42);     // T = i32, returns 42
    var b = identity<f64>(3.14);   // T = f64, returns 3.14
    return a;
}

Multiple Type Parameters

Functions can have multiple type parameters:

function pair_sum<A, B>(a: A, b: B) A {
    return a + b;
}
 
function main() i32 {
    return pair_sum<i32, i32>(10, 20);  // 30
}

Inferred Type Arguments

When a type parameter appears in a parameter's type, the call can leave it out and the argument supplies it. A call may also write only the leading type arguments; the rest are inferred. This is how a function whose return type is a type parameter is called:

function identity<T>(x: T) T { return x; }
function narrow<T, U>(x: U) T { return _convert<T>(x); }
 
function main() i32 {
    var a = identity(42);          // T = i32, from the argument
    var ms: i64 = 200;
    var b = narrow<i32>(ms);       // T = i32 as written, U = i64 from ms
    return a + b;
}

A type argument that is written always wins over what the argument suggests; a type parameter that appears in no parameter must be written. Generic methods work the same way:

class Box<T> {
    var v: T;
    function init(v: T) { this.v = v; }
    function twice<U>(x: U) U { return x + x; }
    function as<R, U>(x: U) R { return _convert<R>(x); }
}
 
var b = Box<i64>(1);
var n = b.twice(21);      // U = i32
var m = b.as<i32>(ms);    // R = i32 as written, U = i64 from ms

Generic Functions with Type Checking

Combine generic functions with _is<T> for type-specific behavior:

function processValue<T>(x: T) i32 {
    if (_is<_Integer>(x)) {
        return 1;
    }
    if (_is<_Float>(x)) {
        return 2;
    }
    return 0;
}
 
function main() i32 {
    var a = processValue<i32>(42);    // Returns 1
    var b = processValue<f64>(3.14);  // Returns 2
    return a + b;  // 3
}

Since Sun uses monomorphization, each instantiation of a generic function compiles to specialized code with dead branches eliminated by LLVM.

Generic Classes

Basic Generic Class

class Box<T> {
  var value: T;
 
  function init(v: T) {
    this.value = v;
  }
 
  function get() T {
    return this.value;
  }
}
 
function main() i32 {
    var intBox = Box<i32>(42);
    var floatBox = Box<f64>(3.14);
    return intBox.get();  // 42
}

Type Parameters

Type parameters are specified in angle brackets after the class name. When instantiating a generic class, you must provide concrete types:

var intBox = Box<i32>(42);      // T = i32
var floatBox = Box<f64>(3.14);  // T = f64

Multiple Type Parameters

Classes can have multiple type parameters:

class Pair<A, B> {
    var first: A;
    var second: B;
 
    function init(a: A, b: B) {
        this.first = a;
        this.second = b;
    }
 
    function getFirst() A {
        return this.first;
    }
 
    function getSecond() B {
        return this.second;
    }
}
 
function main() i32 {
    var p = Pair<i32, f64>(42, 3.14);
    return p.getFirst();  // 42
}

Generic Classes Implementing Interfaces

Generic classes can implement interfaces, forwarding type parameters:

using sun;
 
class Container<T> implements IIterator<T, Container<T>> {
    var items: array<T>;
    var index: i32;
    var size: i32;
 
    function init(arr: ref array<T>, sz: i32) {
        this.items = arr;
        this.index = 0;
        this.size = sz;
    }
 
    function next(self: ref Container<T>) Option<T> {
        if (this.index >= this.size) {
            return Option.None;
        }
        var result = this.items[this.index];
        this.index = this.index + 1;
        return Option.Some(result);
    }
}

Monomorphization

All generics in Sun are monomorphized at compile time. A separate version of the class or function is generated for each type combination used. This provides zero-cost abstractions — generic code runs at the same speed as hand-written type-specific code.

// Using Box<i32> and Box<f64> generates two separate struct types:
var a = Box<i32>(42);     // Generates Box_i32
var b = Box<f64>(3.14);   // Generates Box_f64