Skip to content
Xen / docs

Search documentation

English · v0.1.0-demo.2

Type to search the documentation.

    한국어
    Xen / learn v0.1.0-demo.2

    Generics and type inference

    Declare a type parameter with <T> to reuse code that stores or passes values of different types. Save this as generics.xen:

    struct Pair<T> { first: T, second: T }
    fn identity<T>(value: T) -> T {
    return value;
    }
    fn main() {
    let pair = Pair<Int> { first: identity(7), second: 9 };
    println(pair.first);
    println(identity<String>("hello"));
    }
    Terminal window
    $ xen run generics.xen
    7
    hello

    In identity(7), the argument determines T. identity<String>("hello") supplies it explicitly. For several parameters, provide all types as <A, B>. Structs and enums can also have type parameters. Methods for a generic type use impl<T> Pair<T> { ... }.

    An empty vector or Option.None does not identify an item type by itself. Write let items: Vec<Int> = []; or let missing: Option<String> = Option.None;.

    Generic function inference uses argument types, not the type of the variable receiving the result. let n: I32 = identity(1); fails because 1 selects Int. Use identity<I32>(1) or identity(i32(1)). Partial type argument lists are unsupported.

    A generic function can store, pass, and return values and work with known shapes such as Option<T> or Vec<T>. It cannot directly apply +, comparisons, or arbitrary fields and methods to an unknown T. For example, a + b inside fn add<T>(a: T, b: T) -> T is rejected. Use a concrete numeric type such as Int, or pass a named function callback that performs the required operation. Traits, bounds, and operator overloading are unavailable.

    A generic function can match an enum or tuple whose shape is already known. It cannot match an unknown T against number literals or a particular enum.

    Even when T is unknown, an Option<T> has known Some and None variants. Save this as generic_shapes.xen. or_default selects a value without arithmetic, and a method on Pair<T> reads its declared field.

    generic_shapes.xen
    struct Pair<T> { first: T, second: T }
    impl<T> Pair<T> {
    fn left(&self) -> T {
    return self.first;
    }
    }
    fn or_default<T>(value: Option<T>, fallback: T) -> T {
    return match value {
    Option.Some(item) => item,
    Option.None => fallback,
    };
    }
    fn main() {
    let pair = Pair<Int> { first: 7, second: 9 };
    println(pair.left());
    let missing: Option<String> = Option.None;
    println(or_default(missing, "untitled"));
    println(or_default(Option.Some(42), 0));
    }
    Terminal window
    xen check generic_shapes.xen
    xen run generic_shapes.xen
    generic_shapes.stdout
    7
    untitled
    42

    In or_default(missing, "untitled"), both arguments use the same T, namely String. Some returns its item; None returns the fallback. The fallback is evaluated before the function call. Use match at the call site if evaluating it would be expensive or have unwanted side effects.

    This program deliberately fails under xen check generic_inference_error.xen:

    generic_inference_error.xen
    fn identity<T>(value: T) -> T { return value; }
    fn main() { let value: I32 = identity(1); println(value); }
    generic_inference_error.stderr
    expected I32, found Int (I64)

    identity(1) chooses Int from the argument 1. The I32 on the left does not change that choice. Specify identity<I32>(1), or convert the argument first with identity(i32(1)).

    A type parameter alone does not define operations

    Section titled “A type parameter alone does not define operations”

    This program also deliberately fails under xen check generic_add_error.xen:

    generic_add_error.xen
    fn add<T>(left: T, right: T) -> T {
    return left + right;
    }
    fn main() { println(add(1, 2)); }
    generic_add_error.stderr
    operation requires a concrete type

    T might be an integer or a type without addition. This release has no traits, bounds, or operator overloading. If integer addition is the task, declare Int parameters. For configurable transformations, pass a concrete named function as a callback, as shown in slices and iterators.

    Generic declarations do not remove copy and move rules. Consult copies, moves, and references for the behavior of the actual type.

    Fix identity(1) in both ways described above. Explain why passing different types to or_default cannot satisfy its single T.