Generics

The type inference engine was built from the ground up to recognize generics. A generic is simply a type parameter in which another type could be slotted in. It’s extremely useful because it allows the type inference engine to remember what the type actually is, unlike any.

type Pair<T> = {first: T, second: T }
-- generics can also have defaults!
type PairWithDefault<T = string> = Pair<T>

local strings: Pair<string> = { first="Hello", second="World" }
local numbers: Pair<number> = { first=1, second=2 }
-- can just treat PairWithDefault as a type that doesnt have generics because it has all of its generics assigned a default!
local more_strings: PairWithDefault = { first = "meow", second = "mrrp" }

Generic functions

As well as generic type aliases like Pair<T>, Luau supports generic functions. These are functions that, as well as their regular data parameters, take type parameters. For example, a function which reverses an array is:

function reverse(a)
  local result = {}
  for i = #a, 1, -1 do
    table.insert(result, a[i])
  end
  return result
end

The type of this function is that it can reverse an array, and return an array of the same type. Luau can infer this type, but if you want to be explicit, you can declare the type parameter T, for example:

function reverse<T>(a: {T}): {T}
  local result: {T} = {}
  for i = #a, 1, -1 do
    table.insert(result, a[i])
  end
  return result
end

When a generic function is called, Luau infers type arguments, for example

local x: {number} = reverse({1, 2, 3})
local y: {string} = reverse({"a", "b", "c"})

Generic types are used for built-in functions as well as user functions, for example the type of two-argument table.insert is:

<T>({T}, T) -> ()

Note: Functions don’t support having defaults assigned to generics, meaning the following is invalid

function meow<T = string>(mrrp: T)
     print(mrrp .. " :3")
end