Luau Guide

Overview

OVERDARE Studio uses Luau, a scripting language extended from Lua. While preserving most of Lua's syntax and functionality, Luau introduces a wide range of features, such as Type Annotations, Type Inference, Compound Assignment, and If Expressions.

These extensions allow developers to write safer and more flexible logic while maintaining high productivity and expressiveness.

Note

  • Variables and functions without the local keyword are still declared in the global scope and can be accessed globally.

  • Type-based autocompletion is partially supported at the moment, and some information may not appear in the autocomplete list.

  • Type inference is partially supported at the moment, and some types may not be accurately recognized.

Type Annotations

Variables

When declaring local variables, you can specify their types as below (except when declaring global variables).

local Gold: number = 1

Functions

You can specify function parameters and return values' types as below (can be used for global functions).

local function Sum(a: number, b: number): number
    return a + b	
end
print(Sum(1, 2))

function AddScalarToVector3(v: Vector3, scalar: number): Vector3
    return Vector3.new(v.X + scalar, v.Y + scalar, v.Z + scalar)
end
local Pos = Vector3.new(50, 0, 10)
print(AddScalarToVector3(Pos, 100))

Variadic Functions

Even variadic functions such as (...) can be annotated with types.

--!nonstrict
local function Sum(...: number): number
    local t, result = {...}, 0
    for i = 1, #t do
        result += t[i]
    end
    return result
end
print(Sum(1, 2, 2, 5))

Tables

Table types can be defined using {}, and you can specify the types for each field in braces to fix the types of values found in a table.

--!nonstrict
local SomeList: {} = { 1, false, "Hello" }

local NumberList: { number } = { 1, 2, 3, 4, 5 }
NumberList[2] = false -- Type 'boolean' could not be converted into 'number'

Table type can also define the types of key and values.

local BoolList: { [string]: boolean } = 
{ 
    Key1 = false, 
    Key2 = true 
}
BoolList["Key2"] = 2 -- Type 'number' could not be converted into 'boolean'

Instance

Objects such as Player, Part, and Instance can also be assigned with types.

--!nonstrict
local function InitPlayer(player: Player)
    player:SetAttribute("Score", 0)    
end

local function SetRandomColor(target: Part)
    local r = math.random(1, 255)
    local g = math.random(1, 255)
    local b = math.random(1, 255)
    target.Color = Color3.fromRGB(r, g, b)
end

local function RemoveAllAttributes(target: Instance)
    for key, value in target:GetAttributes() do
        target:SetAttribute(key, nil)
    end
end

Type Checking

Autocomplete Integration

By declaring types for variables or functions, the autocomplete function will also display type information while coding, preventing errors and improving code maintainability.

Inference Modes

You can specify the type inference modes such as --!nonstrict at the top of the script.

Inference Modes
Features

--!nocheck (default)

Completely disables type checking.

--!nonstrict

Checks only the explicitly specified types.

--!strict

Infers and checks types for every code.

--!nocheck

The default state where type checking does not function. (Type errors are ignored, and type inference or warnings do not occur.)

--!nonstrict

Checks only explicitly specified types, and skips variables or functions without specified types.

--!strict

Infers and checks types for every code. Even without specified types, it infers automatically to detect errors.

Flexible Type System

Optional Type

Appending ? after a type makes it optional. Optional types accept both the specified types and nil values.

--!nonstrict
local NumOrNil: number? = 1
NumOrNil = nil

local Num: number = 1
Num = nil -- Type 'nil' could not be converted into 'number'

Type Cast

An error may occur if you assign values to variables of different types. In such cases, you can explicitly cast the type using the :: operator to avoid type errors.

--!nonstrict
local SomeNum: number = 100

local NumToString1: string = SomeNum::any
local NumToString2: string = SomeNum -- Type 'number' could not be converted into 'string'

Literal Type Specification

Types such as string or boolean can be specified as literals, allowing them to be used like constants.

--!nonstrict
local SomeString: "ConstString" = "ConstString"
local SomeBoolean: true = true

SomeString = "Test"  -- Type '"Test"' could not be converted into ''"ConstString"''
SomeBoolean = false  -- Type 'false' could not be converted into 'true'

Unions and intersections

Using union and intersection types, you can allow a variable to accept multiple types or define a new composite type by combining multiple types.

Union types use the | operator to allow a variable to have a single value among multiple types.

--!nonstrict
local NumberOrString: number | string = 10
NumberOrString = "Test"
NumberOrString = false -- Type 'boolean' could not be converted into 'number | string'; none of the union options are compatible

local SomeString: "Hello" | "World" = "Hello"
SomeString = "World"
SomeString = "Test" -- Type '"Test"' could not be converted into '"Hello" | "World"'; none of the union options are compatible

Intersection types use the & operator to define a composite object type by combining multiple types. (Each type must be defined using the type keyword before combining.)

type Type1 = { Name: string }
type Type2 = { Value: number }
local StringAndNumber: Type1 & Type2 = { Name = "Hello", Value = 10 }

local StringAndNumber: Type1 & Type2 = { Name = "Hello", OtherKey = 10 }
--[[
Type
  'StringAndNumber'
could not be converted into
  'Type1 & Type2'
caused by:
  Not all intersection parts are compatible.
  Table type 'StringAndNumber' not compatible with type 'Type2' because the former is missing field 'Value'
]]--

Syntax & Expressions

Compound Assignment

You can use compound assignment listed in the table below to combine operations and assignments into a single statement, which allows code to be written more concisely and efficiently.

However, unlike in other languages, compound assignments cannot be used in expressions such as print (a += 2). They must be written as separate statements like a += 2.

Operator
Features

+=

a = a + b

-=

a = a - b

*=

a = a * b

/=

a = a / b

//=

a = a // b

%=

a = a % b

^=

a = a ^ b

..=

a = a .. b

local Value = 3
Value += 1 -- 4

local Value = 3
Value -= 1 -- 2

local Value = 3
Value *= 2 -- 6

local Value = 3
Value /= 2 -- 1.5

local Value = 3
Value //= 2 -- 1

local Value = 3
Value %= 2 -- 1

local Value = 3
Value ^= 2 -- 9

local Text = "Hello"
Text ..= " World!" -- Hello World!

if Expressions

You can insert literal values within conditional branches to return values immediately based on the conditions.

local RandomNum = math.random(1, 2)
local Result = if RandomNum == 1 then "true" else "false"
	
print(RandomNum, " -> ", Result)

continue Keyword

Within a loop statement such as for or while, the continue keyword can be used to skip the current loop and move on to the next.

for i = 1, 5 do
    if i > 3 then
        continue
    end
    print(i)
end

String interpolation

You can use backticks (`) to dynamically insert variables or expressions within braces.

local ItemName = "Sword"
local ItemPrice = 2000
print(`ItemName : {ItemName} / ItemPrice : {ItemPrice}`) -- ItemName : Sword / ItemPrice : 2000

Loop Statement

Generic For Loops

Without explicitly using iterators like ipairs or pairs, you can directly traverse collections like tables using the for ... in syntax. This can also be applied to array or dictionary structures.

local NumberList = { 1, 2, 3 }
for key, value in NumberList do    
    print(key, " : ", value)
end
local PlayerData = 
{
    Name = "Player",
    Level = 5,
    IsValid = true,
    EquipItemIDList =
    {
        1, 3
    }
}

for key, value in PlayerData do
    print(key, " : ", value)
end

Generalized Iteration

By implementing the __iter metamethod, you can embed iterator logic directly within a table, enabling user-defined iteration behavior.

--!nonstrict
local NumberList = { 1, 5, 11, 4, 9 }

local SortedIterator = 
{
    __iter = function(t)
        local sorted = table.clone(t)
        table.sort(sorted)

        local i = 0
        return function()
            i += 1
            if i <= #sorted then
                return i, sorted[i]
            end
        end
    end
}
setmetatable(NumberList, SortedIterator)

for key, value in NumberList do
    print(key, " : ", value)
end

User-Defined Types

Type

You can declare user-defined types using the type keyword. This allows for safer and more efficient management of complex data structures such as monsters, skills, or tiles.

--!nonstrict
type Car = 
{
    Name: string,
    Speed: number,
    Drive: (Car, boolean) -> () -- function
}

local function DriveFunc(self, useBooster)
    print(self.Name, "Speed : ", self.Speed, " / useBooster : ", useBooster)
end

local Taxi: Car =  
{
    Name = "Taxi",
    Speed = 30, 
    Drive = DriveFunc
}

Taxi:Drive(true)

You can also use a function type across multiple functions, helping to maintain consistent function structures and enabling broader extensibility.

--!nonstrict
type MathFunc = (number, number) -> number

local Sum: MathFunc = function(a, b)
    return a + b
end

local Multiply: MathFunc = function(a, b)
    return a * b
end

Type Exports

When you use the export type keyword, types defined in module scripts can be separated and managed for use outside the module.

--!nonstrict
export type Item = 
{
    Name: string,
    Price: number
}

export type Skill = 
{
    Name: string,
    IsActiveSkill: boolean
}
--!nonstrict
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local ModuleScript = require(ReplicatedStorage.ModuleScript)

local SomeItem: ModuleScript.Item = 
{
    Name = "Sword",
    Price = 10
} 
print(SomeItem)

local SomeSkill: ModuleScript.Skill = 
{
    Name = "FireBall",
    IsActiveSkill = true
}
print(SomeSkill) 

Generic

Generics

By defining generic types using <T>, you can dynamically specify the input type. This makes it possible to express and reuse various forms of data structures flexibly through a single type definition.

--!nonstrict
type SomeData<T> = 
{
    Name: string,
    Value: T
}

local NumberData: SomeData<number> = 
{
    Name = "Test1",
    Value = 15
}
print(NumberData.Name, " / ", NumberData.Value)

local BooleanData: SomeData<boolean> = 
{
    Name = "Test1",
    Value = false
}
print(BooleanData.Name, " / ", BooleanData.Value)

Function Generics

By applying generics to a function's parameters, the type of the data being passed can be dynamically specified at the time of the function call, enabling both high code reusability and type safety.

--!nonstrict
type SomeList<T> = { T }

local NameList: SomeList<string> = { "Bob", "Dan", "Mary" }
local NumberList: SomeList<number> = { 1, 2, 3 }

local function printList<T>(list: SomeList<T>)
    for key, value in list do
        print(key, " : ", value)
    end
end

printList(NameList)
printList(NumberList)

Libraries

Some of Lua's standard libraries, such as io and package, have been removed, while libraries like table and string have been extended. (More details on the libraries will be provided in the future.)

Table Cloning Function

local T1 = { 1, 2, 3 }
local T2 = table.clone(T1)

T1[1] = 10
print(T1[1])
print(T2[1])

String Splitting Function

local SomeString = "Hello,World"
local Splits = SomeString:split(",")
print(Splits[1], Splits[2])

Exit Coroutine

local co = coroutine.create(function()
    for i = 1, 5 do
        wait(1)
        print(i)
    end
end)

coroutine.resume(co)
wait(4)

coroutine.close(co)

Task

local SomeTask = task.spawn(function()
    for i = 1, 10 do
        wait(2)
        print(i)
    end
end)

print("wait 5s")
local elapsed = task.wait(5)

task.cancel(SomeTask)
print("cancel! / elapsed : ", elapsed)

Learn More

Conveniently Managing Coroutine Using Task

Last updated