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.
Related Topics
- 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.