Note: None of this is implemented.

Generics enable the creation of generic function or type logic that works across all types that conform to specified constraints. The compiler automatically specializes this logic to fit every type you apply to the generic code. Generics improve programmer productivity by making it easier to define and use reproducible, type-flexible logic.

Collection types such as lists and dictionaries are well served by this approach, as a single generic can apply the same algorithmic logic across all types the collection stores or indexes with. The usefulness of generics extends well beyond collections. For example, the built-in Result and Option types are also implemented using generics.

Generic Functions

A generic function definition resembles a regular function definition, just adding a list of type names it is generic over. For example, this simple generic function returns the larger of two values:

fn max[T](a T, b T) T:
  if a > b {a} else {b}

The use of square brackets after the function name indicate this is a generic function. T represents some arbitrary type. In this example, the function expects that both its parameters, and its return value, have a value of this type.

We can use this function on values of different types:

imm val1 = max[i32](4, 6)      // 10
imm val2 = max[f32](2.0, 5.0)  // 7.0

In effect, the compiler will create multiple max functions, substituting the specified type everywhere the generic function made use of the type T. In this case, it will create a max function that works with i32 values and another that works with f32 values.

Generic Type Inference

When using a generic function, we do not need to specify the type for T if it can easily be inferred based on the type(s) of the parameter values. Thus, this simple use is possible:

imm val1 = max(4, 6)

Generic Types

Generic types work similarly to generic functions. They look like regular type definitions but specify a list of types they are generic over.

For example, one can define something like the built-in Option type using a generic enum:

union Option[T]:
  struct Some:
    value T
  struct None:

With Option declared, we can use it to specify the type for some values:

mut anint Option[i32]
mut someint Option[i32]
mut afloat Option[f32]

anint and someint have the same type: a nullable integer. However, afloat's type, a nullable float, is different. Rather than having to declare a new type for every variation, a single generic type declaration supporting type substitution can get the same job done more easily.

Once declared, a generic type may be used anywhere any other declared type may be used, including a generic type as a parameter into another generic type.

Generic Struct

A generic struct may be declared in the same way. This generic type defines a resizeable collection:

struct Vec[T]:
	used usize
	arr &[]so T

And now we can use it:

mut string Vec[u8]
mut pointers Vec[&so Node]

Generic Methods

A regular or generic type may define and use generic methods. Generic methods look and act much like generic functions.

Multiple Types

A generic type (or function) can substitute more than one type:

enum Result[T,E]:
  Ok T
  Err E

Once again, we can use this generic declaration to create specialized result types for different values:

mut i32OrBad Result[i32, BadArith]
int openFile Result[FileHandle, IoError]

Generic Constraints

Constraints act as a early-warning system, helping to determine whether generic logic can be successfully applied to any given types. For example, the max generic function cannot be applied to a type that does not support the comparison operators.

Supertypes as constraints

The simplest form of generic constraint is to specify a Supertype when declaring the generic types:

trait Comparable:
	fn `>`(a Self, b Self) Bool
	
fn max[T Comparable](a T, b T) T:
  if a > b {a} else {b}

The trait Comparable applies to any type that implements the greater-than operator on two values of its type and returns either true or false. By constraining T to comply with the Comparable trait constraint, we get a clearer error should we try to apply max to a type that does not implement the greater than operator.

Combining supertypes

Multiple supertypes may be specified as constraints for a single generic variable using +. This ensures the supertype complies with all of their requirements:

fn some[T Comparable + Float](a T):
  ...

Where clause

Some type constraints, such as relationships between two generic types, cannot be expressed within the square brackets. These constraints should be expressed as part of a where clause just before the start of the block.

struct Xyz[T, Y] where T < Y && Option[T] is Node

This expresses the criteria that T is a supertype of Y and that a nullable T is a valid Node.

_