Skip to main content

Generics

Zynx provides type abstraction via monomorphized generics, ensuring zero runtime overhead.

This section explains how to define and use generic structs, functions, and type aliases, how to constrain type parameters with interfaces, and how generics are lowered by the compiler.

Generics interact closely with other parts of the language:

  • Lambdas – for non‑capturing callbacks and adapters that often work with generic types.
  • Memory Model – for understanding how ownership and lifetimes behave in generic code.
  • Scoping and Shadowing – for patterns that appear frequently in generic APIs.
  • Compiler Internals – for details on monomorphization and code generation.

Generic Structs

Define a struct that takes one or more type parameters.

struct Box<T> {
value: T
}

let b = Box<i32> { value: 42 };

Generic Functions

Functions can also be parameterized with types.

fn identity<T>(x: T) -> T {
return x;
}

let x = identity(20); // Type inference supported

Generic Type Aliases

Type aliases can also take generic parameters.

type Vec4<T> = T[4];
type IOResult<T> = Result<T, IoError>;
type Handle<T> = distinct usize;

Generic aliases are expanded during normal type analysis and monomorphization, just like generic structs and functions.

Transparent generic aliases remain interchangeable with their instantiated underlying type. Generic distinct aliases remain nominal after instantiation.

Interfaces and Constraints

Generic type parameters can be constrained by interfaces to enable specific operations.

interface Display {
fn to_string(self) -> str;
}

// T must implement Display
fn print_thing<T: Display>(thing: T) {
os.write(thing.to_string());
}

Operator Constraints

Interfaces can also define operator overloads.

interface Addable<T> {
fn +(self, other: const &T) -> T;
}

fn add_any<T: Addable<T>>(a: T, b: T) -> T {
return a + b;
}

Implementation Details

The Zynx compiler monomorphizes each concrete instantiation into specialized C code. Box<i32> and Box<str> become distinct structs in the final output, ensuring type safety and peak performance.

Monomorphization follows the same overall pipeline as other code:

  • Generic definitions are type‑checked once.
  • Concrete instantiations are expanded into specialized AST nodes.
  • Code generation emits distinct C types and functions per instantiation.

For a deeper look at the compilation pipeline and how generics are lowered, see Compiler Internals.

  • Lambdas – non‑capturing function expressions that often serve as generic callbacks.
  • Memory Model – ownership, moves, and borrows in generic code.
  • Scoping and Shadowing – common patterns when working with generic helpers and unwrap flows.