Skip to content

Volume 09: Generics

1. Introduction

This volume defines generic programming in the Rethon programming language. It describes the generic programming model, type parameters, generic functions, generic structs, generic classes, generic protocols, protocol constraints, multiple constraints, associated types in generics, generic type inference, specialization, existentials and type erasure, generic constraints and visibility, generic errors and diagnostics, and future extension points.

This document is part of the official Rethon Language Specification. It is normative rather than descriptive. Implementations, tooling, documentation systems, and other source-processing tools shall treat the rules in this volume as authoritative.

This volume does not define ABI details, LLVM lowering, backend representation, or runtime-specific generic mechanisms. It defines the source-level and language-level behavior of generics as programmer-visible abstractions.

Rethon is Python-inspired in language philosophy and Python-first in surface syntax where practical. It uses static typing, protocol-oriented generic constraints, structs as value types, classes as reference types, protocols as behavior contracts, associated types, generic specialization where practical, and existential types where abstraction is required.

Generics shall be a central part of the language design.

Protocols shall be the canonical generic constraint mechanism.

2. Generic Programming Model

Generics shall allow functions, structs, classes, and protocols to be written abstractly over one or more types.

A generic declaration shall introduce one or more type parameters that stand for types to be supplied later.

Example:

def identity<T>(value: T) -> T:
    return value

Generic programming shall preserve static typing.

A generic declaration shall remain type-safe for all admissible type arguments.

Generic code shall be readable, explicit, and compatible with the language's value-oriented and protocol-oriented design.

Generic constraints shall ordinarily be protocol-based.

Generic specialization shall be preferred where practical for performance and clarity.

Existential types and type erasure shall be used when abstraction is intentional and concrete type preservation is not required.

3. Type Parameters

A type parameter is a named placeholder for a type supplied to a generic declaration.

Type parameters shall be declared in angle-bracket syntax following the generic declaration name.

Example:

def identity<T>(value: T) -> T:
    return value

Type parameter names shall be identifiers and should normally use PascalCase.

A type parameter may appear in parameter types, return types, field types, associated type bindings, and other type positions permitted by the language.

A type parameter shall not introduce storage by itself.

A type parameter shall be resolved statically according to the rules of the type system and the generic declaration context.

A type parameter may be constrained by one or more protocols.

Example:

def max<T: Comparable>(a: T, b: T) -> T:
    if a.compare_to(b) >= 0:
        return a
    return b

4. Generic Functions

Generic functions shall be supported.

Example:

def identity<T>(value: T) -> T:
    return value

A generic function shall apply its type parameters to its parameter types, return type, nested declarations, and local type positions where permitted.

A generic function shall remain statically typed for each admissible instantiation.

A generic function may use protocol constraints to require behavior from its type parameters.

Example:

def describe<T: Printable>(value: T) -> str:
    value.print()
    return "described"

Generic functions shall preserve the existing function model's requirements for explicit parameter and return typing, unless a later language rule explicitly permits omission in a controlled context.

5. Generic Structs

Generic structs shall be supported.

Example:

struct Box<T>:
    value: T

A generic struct shall remain a value type.

Generic struct values shall preserve value semantics, copy semantics, and the field rules established elsewhere in the specification.

Generic struct declarations shall be suitable for containers, wrappers, records, and other data-oriented abstractions.

A generic struct may use protocol constraints on its type parameters.

Example:

struct Pair<T: Printable, U>:
    first: T
    second: U

Generic structs shall be preferred over class-based alternatives when the abstraction is primarily data-oriented and does not require identity.

6. Generic Classes

Generic classes shall be supported.

Example:

class Cache<T>:
    value: T

A generic class shall remain a reference type.

Generic class values shall preserve reference semantics across type instantiations.

Generic classes shall be suitable for identity-bearing abstractions such as caches, services, coordinators, adapters, and other reference-oriented designs.

A generic class may use protocol constraints on its type parameters.

Example:

class Registry<T: Hashable>:
    items: int = 0

Generic classes shall remain secondary to generic structs when the abstraction is purely data-oriented.

7. Generic Protocols

Generic protocols shall be supported.

Example:

protocol Iterator<Item>:
    def next(self) -> Item?

A generic protocol shall use type parameters to describe behavior contracts that vary by type.

A generic protocol may declare associated types as an alternative or complement to generic parameters where that produces a clearer contract.

Generic protocols shall remain behavior contracts and shall not define instance storage.

Generic protocols shall be usable in generic constraints, protocol compositions, and existential forms when the language syntax permits it.

8. Protocol Constraints

Protocol constraints shall be the canonical way to restrict generic parameters.

Example:

def print_item<T: Printable>(item: T) -> None:
    item.print()

A protocol constraint shall require that the type argument conform to the named protocol.

A type parameter may have a single protocol constraint or multiple protocol constraints as defined by this volume.

Protocol constraints shall be checked statically.

Protocol constraints shall support the language's explicit conformance model and shall not require hidden structural inference for ordinary code.

9. Multiple Constraints

A type parameter may be constrained by multiple protocols.

Example:

def store_and_print<T: Printable & Hashable>(value: T) -> None:
    value.print()
    _ = value.hash()

Multiple constraints shall represent the combined requirements of the listed protocols.

Protocol composition shall be the preferred source form for multiple constraints.

A multiple-constraint type parameter shall satisfy all listed protocol requirements.

A type parameter may also participate in additional language-defined constraints such as associated type bindings when the language syntax permits them.

10. Associated Types in Generics

Associated types shall integrate with generics.

Example:

protocol Iterable:
    associatedtype Item
    def next(self) -> Item?

def first_or_none<T: Iterable>(value: T) -> T.Item?:
    return value.next()

A generic declaration may refer to associated types through protocol constraints.

Associated types shall allow protocols to describe relationships between types without requiring a fully erased model.

Associated types shall be usable in return types, parameter types, and other type positions permitted by the language.

Generic code that depends on associated types shall remain statically checkable.

11. Generic Type Inference

Generic type inference shall be supported when the type arguments can be determined unambiguously from context.

Example:

def identity<T>(value: T) -> T:
    return value

let result = identity(42)

Type inference shall not obscure important generic facts.

If the compiler cannot determine the type arguments safely and unambiguously, the program shall be diagnosed and the programmer shall supply the missing type information.

Type inference shall preserve nullability and protocol constraints.

Type inference shall be especially useful for local declarations and call sites where the generic declaration makes the intended type obvious.

Type inference shall not replace explicit generic contracts on public APIs.

12. Specialization

Generic specialization shall be preferred where practical.

Example:

def swap<T>(a: T, b: T) -> T:
    return b

Specialization means that a generic declaration may be instantiated in a way that preserves strong compile-time type information and enables efficient native code generation.

Specialization shall be preferred when it improves performance, preserves readability, and does not weaken semantic clarity.

Specialization shall not require ordinary source code to mention backend representation details.

The specification does not require a particular compilation strategy for specialization. It only requires that source-level behavior remain consistent and that practical specialization remain a supported language direction.

13. Existentials and Type Erasure

Existential types shall be supported where abstraction is required.

Example:

let value: Printable = Logger()

An existential type shall represent a value through a protocol interface without exposing the concrete type at the use site.

Type erasure shall be supported as an explicit technique, but it shall not be the preferred ordinary model when a generic declaration can preserve type information more directly.

Existentials and type erasure shall be used when heterogeneous storage, abstraction boundaries, or API design require hiding the concrete type.

Existentials shall remain explicit and shall not replace generics as the default performance-oriented abstraction model.

14. Generic Constraints and Visibility

Generic constraints shall remain visible in source code where they are relevant to understanding the API contract.

A generic declaration that requires a protocol constraint shall state that constraint at the declaration site when the language syntax permits it.

Public generic declarations shall use explicit constraints whenever those constraints are part of the visible contract of the API.

Example:

def max<T: Comparable>(a: T, b: T) -> T:
    if a.compare_to(b) >= 0:
        return a
    return b

Constraints shall not be hidden in ways that make public generic APIs difficult to read or reason about.

The type system may infer some local generic details, but public contracts shall remain explicit where clarity matters.

15. Generic Errors and Diagnostics

Generic errors and diagnostics shall be precise and source-oriented.

If a type argument does not satisfy a constraint, the compiler shall diagnose the failure at the location that makes the violation visible where practical.

If type inference cannot determine the necessary generic arguments, the compiler shall report that the type is ambiguous or insufficiently constrained.

If a generic declaration is used inconsistently with its parameterization, the compiler shall report the mismatch clearly.

Examples of diagnostic conditions include:

  • constraint violations
  • ambiguous type arguments
  • missing required type arguments where inference cannot resolve them
  • inconsistent associated type bindings
  • incompatible existential usage

Diagnostics shall support readability and maintainability by helping the programmer understand both the failure and the required fix.

16. Future Extensions

This volume defines the current direction for generics in Rethon, but future specification work may refine the model in carefully controlled ways.

Possible future extensions include:

  • richer syntax for nested constraints
  • additional forms of associated type binding
  • improved inference heuristics for complex generic expressions
  • more explicit existential syntax if needed
  • specialization control annotations if future optimization work warrants them
  • additional protocol-composition syntax for generic constraints
  • future generic metadata needed by tooling, provided it remains source-visible and contract-based

Any future extension shall preserve the current direction:

  • generics apply to functions, structs, classes, and protocols
  • protocol constraints are canonical
  • associated types remain supported
  • specialization is preferred where practical
  • existential forms remain explicit
  • type erasure remains an explicit technique rather than the default model
  • generic code remains statically checked and readable

17. Summary

Generics shall be a central language feature in Rethon.

Type parameters shall allow functions, structs, classes, and protocols to be written abstractly while remaining statically typed.

Protocol constraints shall be the canonical way to restrict generic parameters.

Associated types shall support expressive protocol-based abstraction.

Generic type inference shall be allowed when it is unambiguous.

Specialization shall be preferred where practical.

Existentials and type erasure shall remain explicit tools for intentional abstraction.

Generics shall therefore reinforce Rethon’s Python-first, static, protocol-oriented, value-oriented design while remaining suitable for efficient native compilation and long-term maintainability.