Generics (Monomorphized)
This document specifies Silk’s parameterized type and declaration syntax.
Silk’s generics are compile-time features: parameterized declarations are monomorphized into concrete, fully specified types and functions at build time (there are no runtime type parameters).
Diagnostics rule:
- Generics are part of the Silk language design, not an out-of-language extension.
- When the current compiler rejects a generic form with
E2016, that means the implementation is incomplete for that generic form, not that the language forbids generics.
Note: Option(T) is a special-case surface form that is treated as sugar for
T? in the Supported forms (see optional). This is still
accepted in the Supported forms even as general-purpose type-parameter generics are
implemented.
Overview#
Silk supports parameterized declarations by allowing a parameter list on
struct, interface, enum, impl, and fn declarations.
In Silk currently:
- Supported: type parameters (
T) and const parameters (N: usize) onstruct/interface/enum/impl, type application in type positions (Name(args...)), and generic functions using a compile-time parameter section separated by;in the signature (fn id(T; x: T) -> T) (including generic methods inimplblocks).
Declaration syntax#
Parameter lists#
The parameter list uses parentheses:
struct Vector(T) {
// ...
}
Rules:
Tis a type parameter.- Type parameters may provide a default type argument using
=: interface Serialize(S = string) { ... }- defaults must be trailing (once a parameter has a default, all subsequent parameters must also have defaults).
- Const parameters are written with an explicit type annotation:
N: usize- const parameters are compile-time integer values and may be used in type
positions such as array lengths (
T[N]) and type applications. - The parameter list may be empty (though it is uncommon):
struct Foo() { ... }.
Supported declaration forms:
struct Name(T, ...) { ... }interface Name(T, ...) { ... }enum Name(T, ...) { ... }type Alias(T, ...) = Target(T, ...);impl Name(T, ...) { ... }impl Name(T, ...) as InterfaceName(T, ...) { ... }
Applying parameters in types#
Parameterized types are referenced using the same call-like syntax in type positions:
Vector(int)
Mutex(Account)
Result(int, string)
Applied Generic Type Qualifiers#
Applied generic type names may be used directly as static member qualifiers after their type arguments are fully known:
import { Buffer } from "std/buffer";
fn main () -> int {
let b = Buffer(u8).empty();
return b.capacity() as int;
}
This form is equivalent to introducing an explicit alias for the instantiation and then calling the member through that alias:
type BufferOfU8 = Buffer(u8);
let b = BufferOfU8.empty();
The qualifier applies the same type-argument rules as type positions: type
arguments must resolve to known types, const arguments must be integer
compile-time arguments, and named imports of generic templates participate in
the same lookup rules as imported non-generic types. A generic type whose
parameters all have defaults may use Name().member(...) or the bare
Name.member(...) qualifier form; both forms instantiate the defaults before
member lookup.
Generic enums (tagged unions)#
Enums may be parameterized and are monomorphized like generic structs.
For enum constructors and match arms, callers typically introduce a local alias for an instantiation and then use that alias to construct and match variants:
enum Result(T, E) {
Ok(T),
Err(E),
}
type R = Result(int, string);
fn main () -> int {
let x: R = R::Ok(123);
return match x {
R::Ok(v) => v,
R::Err(_) => 0,
};
}
Default type arguments:
- When a parameterized declaration provides default type arguments, a use site may omit trailing arguments that have defaults.
- If all parameters have defaults, the type may be referenced as
NameorName()(both are equivalent to applying the defaults).
Type arguments may be:
- types (e.g.
int,&Foo,Option(string)). - integer literals for const parameters (e.g.
Vector(u8, 1024)).
Const arguments are compile-time integer literals and participate in monomorphization identity.
Interfaces and applied interface types#
Interfaces may be generic:
interface Channel(T) {
fn send(value: T) -> bool;
fn recv() -> T?;
}
An impl ... as ... conformance may apply type arguments to the interface:
struct QueueU8 {
// ...
}
impl QueueU8 as Channel(u8) {
// ...
}
Rule: when a generic interface is referenced in a concrete impl X as I(...),
all interface type arguments must be fully known at that conformance site. The
only exception is when the conformance itself is generic (type parameters are
in scope), for example:
struct Data(T) { /* ... */ }
interface DataInterface(T) { /* ... */ }
impl Data(T) as DataInterface(T) {
// ...
}
Impl blocks for generic structs#
If a struct is declared with type parameters, its impl blocks must also declare those parameters:
struct Data(T) { /* ... */ }
// OK:
impl Data(T) { /* ... */ }
// Error:
// impl Data { /* ... */ }
This rule keeps method receiver typing unambiguous and makes monomorphization explicit.
Functions#
Generic functions require a way to declare type/const parameters distinct from value parameters. The initial parsed surface form is:
fn get_first(T, N: usize; v: &T[N]) -> T {
// ...
}
Where the ; separates generic parameters from value parameters inside the
function’s parameter list.
Alternate (Go-like) function declaration syntax#
Silk also supports a Go-like generic header form:
fn (T, N: usize) get_first (v: &T[N]) -> T {
// ...
}
This is sugar for the ; form above; the compiler records the same generic
parameter list (T, N: usize) either way.
Rules:
- At most one generic parameter list may be provided:
- either
fn name(T; ...), - or
fn (T) name (...).
Call syntax for generic functions#
Calls mirror the signature split:
let x: int = get_first(int, 4; &xs);
Rules:
- the
;separates compile-time arguments from runtime value arguments, - compile-time arguments are a comma-separated list of:
- type arguments (
int,&Foo,Option(string)), - and integer literals for const arguments,
- runtime arguments are ordinary expressions.
- the compile-time argument list may be empty when defaults supply all generic
parameters, for example
id_default(; 1)uses the default type argument forTinfn id_default(T = int; x: T) -> T.
Call-site type inference (omitting ;)#
When a call does not include the generic separator (;), the compiler may
infer type and const arguments from runtime arguments:
fn (X, Y) add (x: X, y: Y) -> X {
return x + y as X;
}
let a = add(1.123, 2); // infers X = f64, Y = int
Rules:
- Both type parameters (
T) and const parameters (N: usize) may be inferred. - Inference is driven by the runtime argument expressions and any types that are known at the call site:
- literals (
123,1.0,"hi",'a',true), - struct literals (
Point { ... }), - explicit casts (
expr as Type), - and name expressions (
x) when the binding’s type is known (from an annotation likelet x: T = ...or from a simple initializer like a literal/struct literal). - Const parameters are inferred only from type structure:
- array lengths (
T[N]), - and const arguments in applied types (
Buffer(T, N)), when the corresponding runtime argument type provides a concrete value. - When inference cannot determine a type argument, compilation fails with an actionable diagnostic. Disambiguate by either:
- inserting
ascasts on runtime arguments, or - using the explicit
;form (add(f64, int; 1.123, 2)). When inference cannot determine a const argument, disambiguate by using the explicit;form (take_buf(4; buf)).
Implementation notes#
- Monomorphization produces a concrete instance for each referenced
instantiation
Name(args...). - Type names share one namespace within a
package:struct,interface,enum,error, andtypedeclarations may not reuse the same name. - Name conflicts across generic arities are rejected (for example,
struct Fooandstruct Foo(T)cannot both exist in the same package namespace). - Const parameters are currently restricted to integer primitive types; const
values are usable in type positions (for example
T[N]) but are not yet exposed as runtime values.
Source repository · Edit this page · View Markdown