Generic Types

    Cyrus supports generic types to create reusable and flexible type constructs. Generics are compile-time constructs, enabling static dispatch. They can be applied to enums, structs, unions, and type aliases.


    1. Generic Type Constructs


    1.1 Generic Enums

    enum Result<V, E = bool> {
        Ok(V),
        Err(E),
    }
    
    • V and E are generic type parameters.
    • E has a default type, allowing omission during instantiation:
    var r1 = Result.Ok<int>(10);         // V=int, E=bool (default)
    var r2 = Result.Ok<int, string>(10); // V=int, E=string
    

    1.2 Generic Structs

    struct Pair<A, B> {
        first: A,
        second: B,
    }
    
    var p = Pair<int, string> { first: 42, second: "hello" };
    
    • Type arguments can be explicit or inferred from context:
    var q: Pair<float, float> = Pair { first: 3.14, second: 2.71 };
    

    1.3 Generic Unions

    union ValueOrError<T> {
        Value(T),
        Error(string),
    }
    
    var v = ValueOrError.Value<int>(100);
    
    • Works like enums but allows single-value variants.

    1.4 Type Aliases with Generics

    type Foo<X, Y> = Pair<Y, X>;
    
    • Reorders generic parameters from the original type.
    • Example usage:
    var f: Foo<int, string> = Foo { first: "hello", second: 42 };
    // Foo<int, string> = Pair<string, int>
    
    • Defaults can be used:
    type DefaultPair<A> = Pair<A, bool>;
    
    fn main() {
        var d: DefaultPair<int> = DefaultPair { first: 10, second: true };
    }
    

    2. Positional and Named Type Arguments


    2.1 Positional

    var r1 = Result.Ok<int>(10);          // V=int, E=bool (default)
    var r2 = Result.Ok<int, string>(10);  // V=int, E=string
    

    2.2 Named

    var r3 = Result.Ok<E = string, V = int>(10);
    
    • Named arguments override defaults regardless of order.
    • Mix positional and named arguments as needed.

    3. Generic Type Inference

    var r4: Result<float> = Result.Ok(3.14); // V=float, E=bool (default)
    

    Compiler resolution order:

    • Explicit type arguments
    • Defaults
    • Expected type context

    If inference fails, the compiler reports an error and requires explicit type arguments.


    4. Combined Example

    struct Pair<A, B> { first: A, second: B }
    type Swap<X, Y> = Pair<Y, X>
    enum Result<V, E = string> { Ok(V), Err(E) }
    
    var p: Swap<int, string> = Swap { first: "hi", second: 42 }
    var r: Result<float> = Result.Ok(3.14)
    var r2: Result<float, bool> = Result.Err(false)
    
    • Swap<int, string> = Pair<string, int>
    • Result<float> uses default E=string

    5. Compile-Time vs Runtime

    Generic types in Cyrus are compile-time constructs, also known as static dispatch.

    • Code for each concrete instantiation is generated at compile time.
    • No runtime type checks for generics.
    • High performance, as calls are monomorphized.

    6. Runtime Polymorphism (Uncompleted)

    • Cyrus also supports runtime polymorphism via other mechanisms (interfaces or trait-like structures, in progress).
    • Generic types are not runtime-polymorphic; they remain static.

    7. Generic Functions (Uncompleted)

    • Cyrus supports generic functions, similar to generic types.
    • Placeholders:
    fn identity<T>(value: T) T {
        return value;
    }