Roblox UtilitiesDevlHub Roblox Documentation

Type checking

Luau uses gradual typing through the use of type annotations and inference.

For the latest and most complete type checking documentation, see here.

Luau supports a gradual type system through the use of type annotations and type inference. These types are used to provide better warnings, errors, and suggestions in the Script Editor.

Define a type#

Use the type keyword to define your own types:

Luau
type Vector2 = {x: number, y: number}

Inference modes#

There are three Luau type inference modes that can be set on the first line of a Script:

  • --!nocheck — Don't check types.
  • --!nonstrict — Only asserts variable types if they are explicitly annotated.
  • --!strict — Asserts all types based off the inferred or explicitly annotated type.

The --!nonstrict and --!strict modes control how strict the type checker is with inferring and checking types for variables and functions. Any type mismatches in scripts are highlighted in the Script Editor and surfaced as warnings in the Script Analysis window.

To set a default mode for all scripts that you can override as needed, see Workspace.LuauTypeCheckMode.

Types#

A type annotation can be defined using the : operator after a local variable, followed by a type definition. By default, in nonstrict mode, all variables are assigned the type any.

Luau
local foo: string = "bar"
local x: number = 5

There are four primitive types that can be used in an annotation:

  • nil - no value
  • boolean - true or false
  • number - a numeric value
  • string - text

Within Roblox, all classes, data types, and enums have their own types that you can check against:

Luau
local somePart: Part = Instance.new("Part")
local brickColor: BrickColor = somePart.BrickColor
local material: Enum.Material = somePart.Material

To make a type optional, use a ? at the end of the annotation:

Luau
local foo: string? = nil

This will allow the variable to be either the specified type (in this case string) or nil.

Literal types#

You can also cast strings and booleans to literal values instead of using string and boolean:

Luau
local alwaysHelloWorld: "Hello world!" = "Hello world!"
alwaysHelloWorld = "Just hello!"  -- Type error: Type '"Just hello!"' could not be converted into '"Hello world!"'

local alwaysTrue: true = false  -- Type error: Type 'false' could not be converted into 'true'

Type casts#

Sometimes, you might need to assist the typechecker by explicitly casting a value to a different type with the :: operator:

Luau
local myNumber = 1
local myString: string

myString = myNumber  -- Not OK; type conversion error
myString = myNumber :: any  -- OK; all expressions can be cast to 'any'
local myFlag = myNumber :: boolean  -- Not OK; types are unrelated

Function typing#

Consider the following function:

Luau
local function add(x, y)
	return x + y
end

This function adds x to y, but errors if one or both of them is a string. Luau doesn't know that this function can only use numbers. To prevent this category of problem, add types to the parameters:

Luau
local function add(x: number, y: number)
	return x + y
end

Luau now knows that the function takes two numbers and throws a warning if you try to pass anything that isn't a number into the function:

Luau
add(5, 10)
add(5, "foo")  -- Type error: string could not be converted into number

To define a return type, put a : operator at the end of the function definition:

Luau
local function add(x: number, y: number): number

To return multiple types, place the types in parentheses:

Luau
local function FindSource(script: BaseScript, pattern: string): (string, number)
	return 42, true  -- Type errors
end

Define a functional type#

A functional type can be defined by using the syntax (in) -> out. Using the functions from the previous examples, the types of the functions are:

Luau
type add = (x: number, y: number) -> number
type FindSource = (script: BaseScript, pattern: string) -> (string, number)

Table types#

Luau does not have a table type; instead, table types are defined using {} syntax. One way of defining tables is using the {type} syntax, which defines a list type.

Luau
local numbers: {number} = {1, 2, 3, 4, 5}
local characterParts: {Instance} = LocalPlayer.Character:GetChildren()

Define index types using {[indexType]: valueType}:

Luau
local numberList: {[string]: number} = {
	Foo = 1,
	Baz = 10
}

numberList["bar"] = true  -- Type error: boolean can't convert to number

Tables can also have explicit string indices defined in a type.

Luau
type Car = {
	Speed: number,
	Drive: (Car) -> ()
}

local function drive(car)
	-- Always go the speed limit
end

local taxi: Car = {Speed = 30, Drive = drive}

Variadics#

Here's a function that calculates the sum of an arbitrary amount of numbers:

Luau
local function addLotsOfNumbers(...)
	local sum = 0

	for _, v in {...} do
		sum += v
	end

	return sum
end

As expected, this function can take any value, and the typechecker won't raise a warning if you provide an invalid type, such as a string.

Luau
print(addLotsOfNumbers(1, 2, 3, 4, 5))  -- 15
print(addLotsOfNumbers(1, 2, "car", 4, 5))  -- Attempt to add string to number

Instead, assign a type to the ..., just like how you assign any other type:

Luau
local function addLotsOfNumbers(...: number)

And now, the second line raises a type error.

Luau
print(addLotsOfNumbers(1, 2, 3, 4, 5))
print(addLotsOfNumbers(1, 2, "car", 4, 5))  -- Type error: string could not be converted into number

However, this does not work when writing a functional type definition:

Luau
type addLotsOfNumbers = (...: number) -> number  -- Expected type, got ':'

Instead, use the syntax ...type to define a variadic type.

Luau
type addLotsOfNumbers = (...number) -> number

Unions and intersections#

You can even define a type as two or more types using a union or intersection:

Luau
type numberOrString = number | string
type type1 = {foo: string}
type type2 = {bar: number}
type type1and2 = type1 & type2  -- {foo: string} & {bar: number}

local numString1: numberOrString = true  -- Type error
local numString2: type1and2 = {foo = "hello", bar = 1}

Define an inferred type#

You can use the typeof function in a type definition for inferred types:

Luau
type Car = typeof({
	Speed = 0,
	Wheels = 4
})  --> Car: {Speed: number, Wheels: number}

One way to use typeof is to define a metatable type using setmetatable inside the typeof function:

Luau
type Vector = typeof(setmetatable({}::{
	x: number,
	y: number
}, {}::{
	__add: (Vector, Vector|number) -> Vector
}))

-- Vector + Vector would return a Vector type

Generics#

Generics are at a basic level parameters for types. Consider the following State object:

Luau
local State = {
	Key = "TimesClicked",
	Value = 0
}

Without generics, the type for this object would be as follows:

Luau
type State = {
	Key: string,
	Value: number
}

However, you might want the type for Value to be based on the incoming value, which is where generics come in:

Luau
type GenericType = T

The `` denotes a type that can be set to anything. The best way to visualize this is as a substitution type.

Luau
type List = {T}

local Names: List<string> = {"Bob", "Dan", "Mary"}  -- Type becomes {string}
local Fibonacci: List<number> = {1, 1, 2, 3, 5, 8, 13}  -- Type becomes {number}

Generics can also have multiple substitutions inside the brackets.

Luau
type Map<K, V> = {[K]: V}

To rework the State object from earlier to use a generic type:

Luau
type State = {
	Key: string,
	Value: T
}

Function generics#

Functions can also use generics. The State example infers the value of T from the function's incoming arguments.

To define a generic function, add a <> to the function name:

Luau
local function State(key: string, value: T): State
	return {
		Key = key,
		Value = value
	}
end

local Activated = State("Activated", false)  -- State<boolean>
local TimesClicked = State("TimesClicked", 0)  -- State<number>

Type exports#

To make it so a type can be used outside of a ModuleScript, use the export keyword:

Luau
export type Cat = {
	Name: string,
	Meow: (Cat) -> ()
}
Luau
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Types = require(ReplicatedStorage.Types)

local newCat: Types.Cat = {
	Name = "metatablecat",
	Meow = function(self)
		print(`{self.Name} said meow`)
	end
}

newCat:Meow()