Skip to main content

Generics

Mux supports generics with type parameters and interface bounds, similar to Go and Rust.

Generic Functions

Functions can be generic over type parameters:

generic_identity.mux
Loading...

Type Constraints

Use the is keyword to constrain type parameters to specific interfaces:

generic_constraints.mux
Loading...

Multiple Type Parameters

multiple_type_params.mux
Loading...

Multiple Bounds (AND Semantics)

Type parameters can have multiple constraints:

multiple_bounds.mux
Loading...

Generic Classes

Classes can be generic over type parameters:

generic_classes.mux
Loading...

Generic Classes with Multiple Type Parameters

generic_pair.mux
Loading...

Built-in Interfaces

Mux provides built-in interfaces for common operations:

InterfaceDescription
StringableTypes that can be converted to string (via .to_string() method)
EquatableTypes that support == and != operators
ComparableTypes that support <, <=, >, >= operators
HashableTypes that can be used as keys in sets and maps
ErrorTypes that can be used as result<T, E> errors (via .message() method)

Operator Mapping

  • a + b - Works for types with native support (int, float, string, list, map, set)
  • a > b - Works for types with native support (int, float, string, char)
  • a == b - Works for all types

Primitives and Interfaces

TypeImplements
intStringable, Equatable, Comparable, Hashable
floatStringable, Equatable, Comparable, Hashable
stringStringable, Equatable, Comparable, Hashable, Error
boolStringable, Equatable, Hashable
charStringable, Equatable, Comparable, Hashable

result<T, E> requires E to implement Error.

Implementing Interfaces for Custom Types

A class opts into a capability by declaring it and writing the method it requires. Equatable is built in - you do not declare the interface yourself - and once a class implements it, == works on that class and it satisfies an Equatable bound:

custom_equatable.mux
Loading...

The four built-in capabilities are Equatable (eq), Comparable (cmp), Hashable (hash and eq) and Stringable (to_string). Comparable and Hashable each grant Equatable, so a class needs only the one that fits. See Classes.

Generic Functions with Collections

generic_collections_funcs.mux
Loading...

Monomorphization

Mux uses compile-time monomorphization for generics - specialized code is generated for each type instantiation:

monomorphization.mux
Loading...

How Monomorphization Works

  1. Type inference: Determine concrete types from function arguments
  2. Name generation: Create unique identifier: FunctionName$$Type1$$Type2$$
  3. Type substitution: Replace type parameters with concrete types
  4. Code generation: Emit specialized function body
  5. Caching: Store generated methods to avoid regeneration

Benefits

  • Zero runtime cost: No boxing, no vtables, no type checks
  • Static dispatch: Methods resolved at compile time
  • LLVM optimization: Each specialization can be fully optimized

Tradeoff

  • Increased intermediate code size and compilation time: One copy per type combination

Type Inference in Generics

Type parameters can often be inferred from context:

type_inference_generics.mux
Loading...

However, when ambiguous, explicit types are required:

explicit_generic_types.mux
Loading...

Generic Enums

Enums can be generic, and each instantiation is monomorphized - Box<int> holds a real int rather than a boxed pointer.

generic_enums.mux
Loading...

optional and result are built in, not user enums. You cannot declare a type named optional or result - that is rejected, because it used to overwrite the built-in silently - and you construct them with some, none, ok and err rather than through an enum name:

Mux
Loading...

See Enums and Error Handling for more details.

Constraints and Bounds

Single Constraint

single_constraint.mux
Loading...

Multiple Constraints (AND)

multiple_constraints.mux
Loading...

Type T must implement all specified interfaces.

Best Practices

  1. Use type constraints - Specify what operations are needed
  2. Prefer interface bounds over concrete types - More flexible
  3. Leverage monomorphization - No runtime overhead for generics
  4. Use descriptive type parameter names - T, U, K, V for simple cases
  5. Explicit types when ambiguous - Helps readability
  6. Keep generic functions simple - Complex logic harder to debug
  7. Document constraints - Make requirements clear

See Also