Luau is an open source general purpose programming language that is developed and maintained by Roblox. Luau is a superset of Lua and introduce many new features most notably typing! It is often used to develop experiences for Roblox. This is guide is mainly written for folks who have some familiarity with programming and would like to start using Luau! If you are new to programming and would like to learn Luau for the purpose of writing your own Roblox game, we suggest you get started on the creator docs site!
We aim to keep as much of this content as general as accessible as possible and many parts will explicity mention how to use Luau with Roblox. Sections that are entirely new to Luau are tagged with Luau
. If you are skimming through the guide because you are already familiar with Lua, these are the sections you want to pay attention to!
This guide takes much inspiration (and some content) from the freely available online Programming in Lua book as well as the resources on the Luau website. Several sections currently link out to the Programming in Lua and you can easily infer the Luau types for the content in that guide after reading this one!
This guide was started as a Roblox Hack Week project. We aim to grow and evolve this guide over time and the current version should already be more than enough to really get you going with Luau! Please open issues or PRs on github if you have any ideas to improve the guide!
- Lua 5.1 manual (5.1 is the version where Luau diverged from Lua).
- Luau lang website
- Roblox Creator Docs site
While Luau can be used as a standalone language, it is almost always embedded as a scripting language in another program such as Roblox. The easiest way to get started with writing and running Luau code is with the Luau online interpreter. The next easiest way is...
To get started with Luau in Roblox, go to the Roblox Creator Hub, log in/make an account, and download Roblox Studio. From the templates tab, create a new empty Baseplate project. Then in the explorer widget, write click under "workspace" and create a new script. To test your script, press the print
commands which we will introduce later) in the Console widget.
The built in Roblox script editor will automatically run the Luau typechecker on your code and tell you if there are any errors
If you are using tools like LFS or Rojo to develop code in Visual Studio Code, you can use the community created Luau Language Server extension to get the same typechecking experience in your editor!
If for some reason you want to use Luau without Roblox (perhaps you are trying to embed Luau as a scripting language for your own project) you can download the interpreter on the Luau Github page and run the code directly. You may also want to try the open source Luau runtime environment Lune to power up your program and make things a lot easier in general too!
If you are not using Luau with Roblox you can skip this section!
Roblox experiences are complex and multi-user and therefore there are a few things you should understand about when and where your Luau scripts run. Each Roblox experience has one or more clients (or players) connected to a server (called RCC). Scripts can run on either client or server. Roblox scripts can affect the game world, and by default, effects generated by server scripts automatically get replicated to all clients whereas effects on client scripts remain local. There are some exceptions to this rule that are beyond the scope of this guide!
Luau scripts are wrapped inside of Instances
in the Roblox engine.
There are 3 types of script Instances
- Scripts: these run on the server only
- LocalScripts: these run on clients only
- ModuleScripts: these do not run by themselves
Anytime a Script
or LocalScript
instance is created (for example, when you launch the experience), the Roblox engine will run the code in the file. You can not control the order in which scripts run. These are the entry points into your Luau program.
ModuleScripts
are reusable modules can be require(...)
'd by other scripts. One major difference when developing on Roblox is that the Roblox runtime environment has a Datamodel rather than a filesystem. Therefore, instead of traversing the filesystem e.g. require(helpers/myhelperscript.luau)
you need to traverse the Datamodel to find your module script e.g. require(GetService("ReplicatedStorage").helpers.myhelperscript)
TLDR to run Luau on Roblox, create a Script
instance and hit the playtest button. Your script will run on the server and you will still be able to see its output on your local console.
Variables are about what you'd expect in Luau. Variables can be assigned to with the =
operator.
Global variables do not need to be declared. You simple assign to them and they get created if they don't already exist or assigned to if they do.
myGlobalVar = "I created a global variable"
To remove a global variable, you simply assign nil
to it
myGlobalVar = nil
In general, an object with a nil
value is equivalent to object not existing in Luau. You will see more of this in the tables section.
Global variables can be accessed via the _G
table i.e. _G["myGlobalVar"]
however if you ever find yourself wanting to do this please just stop.
Using global variables is discouraged by 9/10 software engineers so instead please avoid globals as much as possible!!
Local variables are variables that are scoped to the block or chunk in which they are declared. Local variables shadow any globals or locals in the parent scope. Local variables are declared with the local
keyword. If you'd like to create a scope (block) for your local variables, you can use the do
control expression
do
local myLocalVariable = "I am a local variable"
print(myLocalVariable)
-- prints
end
print(myLocalVariable) -- prints nil
All variables in Luau are typed either implicitly or explicitly. We'll go into this more in the typing section.
local myExplicitlyTypedNumber : number = 76
local myImplicitlyTypedNumber = 32
Luau contains the standard control structures you would expect in a programming language. There are some subtleties to how expressions are evaluated but otherwise this is mostly what you'd expect from a programming language. Feel free to skim this section and then jump to the Gotchas section at the end.
This section covers relational and logical operators. String and table expressions are covered in later sections.
Luau supports if
, else
and elseif
for conditionally executing code. The syntax is as follows:
if condition1 then
-- code to execute if condition1 is true
elseif condition2 then
-- code to execute if condition2 is true
else
-- code to execute if neither condition1 nor condition2 is true
end
In Luau, any value may be used as a condition. Conditionals (e.g. if <condition>
) regard false
and nil
as false and everything else as true including 0
and the empty string ""
.
The numeric for
loop is used to iterate over a range of numbers. The syntax is as follows:
for i = start, stop, step do
-- code to execute
end
where start
, stop
, and step
are Luau expressions that evaluate to numbers (often just number literals). The loop will start at start
, increment by step
until it equals stop
. If step
is not provided, it defaults to 1
. If step
is negative, the loop will decrement instead of increment.
for i = 1, 5 do
print("meow ")
end
-- prints "meow meow meow meow meow"
There are some subtleties to the numeric for
loop. Please see Programming in Lua 4.3 for more info.
Luau also supports generic for using iterators. These are fuctions that return the next value in a sequence which the for
will iterate over. Writing your own iterators is an advance topic. Please see Programming in Lua 7.1 for more info.
Generic for is often used to iterate over the elements of a table. In Luau it is possible to iterate over tables directly with for
. This is covered in the table section later.
-- Lua way using iterator returned by `pairs` function
for key, value in pairs(myTable) do
-- code to execute
end
-- Luau way
for key, value in myTable do
-- code to execute
end
This is more or less what you would expect:
local x = 0
while x < 10 do
print("meow ")
x = x + 1
end
-- prints "meow meow meow meow meow meow meow meow meow meow"
This is Luau's "do until" loop. It is similar to a while
loop except the condition is checked at the end of the loop. This means the loop will always execute at least once.
repeat
print("meow ")
until false
-- prints "meow"
The break
statement is used to exit a loop early. It can be used in any loop construct.
local x = 0
while true do
print("meow ")
x = x + 1
if x == 5 then
break
end
end
-- prints "meow meow meow meow meow"
TODO
The most important addition to Luau is the addition of types. Types add an additional level of correctness to you program! The addition of types to your pogram does not affect the way your pogram runs--instead, it allows the Luau type checker to statically analyze your code and help ensure everything is correct.
In Roblox, the Luau type checker is set to run in nonstrict mode by default. We recommend enabling strict mode by adding the --!strict
directive to the top of your luau script. If you are using Luau outside of Roblox or with a third party tool like Rojo, there may be other configuration files you can use to globally set what mode of the typechecker
Luau Typechecker Modes (not important)
- --!nocheck,
- do not run the typechecker
- --!strict
- (recommended) run the type checker in strict mode
- --!nonstrict
- (default) similar to strict mode, but is more relaxed in inferring types.
To declare the type of a variable, use the :
symbol. For example, to declare a variable of type number
(types are covered later) we can do the following:
local myTypedNumber : number
Variables in functions are type in the same way, and the return type is added at the end:
local function parrot(wordToSay : string, numberTimes : string) : string
local output : string = ""
for i = 1, numberTimes, 1 do
output = output .. wordToSay
end
return output
end
Luau will also infer types for you.
local x = 5
print(typeof(x)) -- prints "number"
It is this author's opinion that you should avoid relying on type inference as much as possible!
NOTE There are some limitations in the current Luau type checker when it comes to type inference and type refinement. There is a new typechecker (to be released someday) that will solve many of these issues!
Types do not affect runtime in anyway
local myTypedNumber : number = 5
print(myTypedNumber) -- prints 5
myTypedNumber = "hello" -- this is a Luau type error and the program will still run just fine!
print(myTypedNumber) -- prints "hello"
Types help ensure your programs correctness. For example consider the following function:
As your program grows in complexity, type errors like the above become increasingly complex and harder to track. Adding types to your pogram is a small investment that will greatly improve your engineering efficiency!
Types also make your code a lot more readablefor others. For example, types allow you to readily read what arguments a function takes and what output to expect from it:
The Luau Typechecking page is a great resource. Please check it out to learn more about advanced typing features!
The Luau comes with 10 primitive types:
nil
string
number
boolean
table
function
thread
userdata
vector
buffer
The first 4 are the very basic types. The remaining types will be covered in other sections
The nil
type can also take the value nil
. The nil
value can still be a value of all other types (except the never
type, more on that later). The nil
type is special and is also rarely useful. Don't worry too much about it.
number
is Luau's only built in number type. They are represented internal with real double precision floating point number. Luau's number should be sufficient for 99.99% percent of your needs!
Here are some examples of valid number expressions
4
1.4723
4.57e-3
0.3e13
8e+22
Luau supports the binary number operators
+
addition-
substraction*
multiplication/
division^
exponentiation
and the unary operator
-
negation
with standard operator precedence.
Luau strings represent a sequence of characters. They are immutable so if you want to change them you need to create a new string. Luau strings are eight-bit clean so they can hold any characters although if you are storing binary data, the buffer
type may be more appropriate.
Luau strings can be concatenated with the ..
operator, and can be passed into the print
function for debugging:
local cuteAnimal = "bunny π°"
print("I am a " .. cuteAnimal) // prints "I am a cute bunny π°" to the console
Luau string literals can be delimited with either doubel quotes ""
or single quotes ''
. These are the same except they allow for unespcaped single quotes or double quotes respectively.
The following special characters can be escaped:
\a
: bell\b
: back space\f
: form feed\n
: newline\r
: carriage return\t
: horizontal tab\v
: vertical tab\\
: backslash\"
: double quote\'
: single quote\[
: left square bracket\]
: right square bracket
Luau comes built in with very useful string manipulation functions in its standard library. You can see a list of methods available here. The Programming in Lua book has a good section on the string
library if you are looking for more guidance.
The Luau boolean
type has two values: true
and false
, these are frequently used in conditional expressions however not all conditionals are booleans. Please see the Logical Expressions section for how to work with booleans.
You can determine the type of a variable with the typeof
function
local x = 5
print(typeof(x)) -- prints "number"
This has various uses beyond telling you the type of a variable which we will cover soon!
unknown
is the "top type", that is itβs a union of all types. An unknown
typed variable can take on any value.
local a: unknown = "hello world!"
local b: unknown = 5
local c: unknown = function() return 5 end
Unlike any
, unknown
will not allow itself to be used as a different type!
local function unknown(): unknown
return if math.random() > 0.5 then "hello world!" else 5
end
local a: string = unknown() -- not ok
local b: number = unknown() -- not ok
In order to turn a variable of type unknown
into a different type, you must apply type refinements on that variable.
local x = unknown()
if typeof(x) == "number" then
-- x : number
end
The never type (not important, just ignore me, seriosuly)
never
is the bottom type, that is the sub type of all types. It is the "dual" of unknown
that is, a value of type never
must be assignable to a variable of any type. There is no such value in Luau. You might think that's nil
and you would be right except that nil
is not a valid value of the never
type. never
is occasionally useful, one such use case is when type refinements proves it impossible:
local x = unknown()
if typeof(x) == "number" and typeof(x) == "string" then
-- x : never
end
any
is just like unknown
, except that it allows itself to be used as an arbitrary type without further checks or annotations. Essentially, itβs an opt-out from the type system entirely.
local x: any = 5
local y: string = x -- no type errors here!
any
is useful when working with untyped code. Otherwise avoid it as much as possible and type your code! If your code is getting so complicated that you find yourself using the any
type because you can't reasonably determine the types you are using that's often a sign you should simplify your code!
Types can be unioned with the |
type operator. For example
function gimmeStringOrNumber(x: string | number)
if typeof(x) == "string" then
print("You gave me a string: " .. x)
else
print("You gave me a number: " .. tostring(x))
end
end
gimmeStringOrNumber("hello") -- prints "You gave me a string: hello"
gimmeStringOrNumber(5) -- prints "You gave me a number: 5"
gimmeStringOrNumber(true) -- not valid
Using symbols from math, we say number
β number | string
to indicate that number
is a subtype of number | string
. We saw earlier that X
β unkown
for ALL types X
.
The above is already very useful and this is not the only way to have subtypes. The subtyping relation will become much more relevant when we cover table types!
<TODO copy from website, add formal definiton for truthy>
Note. There are some limitations in the current Luau type checker when it comes to type inference and type refinement. There is a new typechecker (to be released someday) that will solve many of these issues!
Functions are first class objects in Luau and have their own type syntax.
To declare a typed function e.g.
local function myTypedFunction(name : string, numApples : number) : string
return arg1 .. " ate " .. tostring(numApples) .. " apples."
end
And to refer to a function as a typed value e.g.
local f : (string, number) -> string
f = myTypedFunction
If your function returns multiple values you can use the following syntax e.g.
-- gimme has type `() -> (number, string)`
local function gimme() -> (number, string)
local things : {string} = {"apple", "orange", "xbox"} -- (the type of `things` is an indexed table type, we will cover it later)
return (math.random(5), things[math.random(#things)])
end
local num, things = gimme()
Letβs start with something simple.
local function f(x) return x end
local a: number = f(1) -- ok
local b: string = f("foo") -- ok
local c: string = f(true) -- not ok
In strict mode, the inferred type of this function f
is <A>(A) -> A
(take a look at generics), whereas in nonstrict we infer (any) -> any
. We know this is true because f
can take anything and then return that. If we used x
with another concrete type, then we would end up inferring that.
Further type inference on functions works (for the most part) like you would hope.
local function greetingsHelper(name: string) : string
return "Hello, " .. name
end
local function greetings(name)
return greetingsHelper(name) -- infers type `(string) -> string` for the variable `name`
end
print(greetings("Alexander")) -- ok
print(greetings({name = "Alexander"})) -- not ok
In general though, it is this author's opinion that it is better to always explicitly type your functions except perhaps in cases where your function is truly generic like in the simple example above.
This is SUPER DUPER important. This is covered in the table section!
You can force a value to be a certain type with the ::
symbol. This is called typecasting. You can only typecast if the value type shares a subtype with the type being casted too. Luau can not verify that the typecast will be valid during runtime. For this reason avoid typecasting as much as possible.
local a : string | number = 1
local b : number = a -- not ok
local c : number = a :: number -- ok
local d : string = a :: string -- ok, but d will not actually be a string
print(d) -- error! can't print a number! Don't typecast!
Typecasting is mainly useful when working with untyped Lua code or when you need to help along the typechecker with inference in some scenarios which will likely no longer be needed when the new typechecker is released.
In Luau, variables are either value or reference types. The value of a variable is the actual data that the variable holds. The reference of a variable is the location in memory where the data is stored.
The following primitive types in Luau are value types:
nil
number
boolean
The remaining primitives are reference types. Note that while string
is a reference type, they behave like value types because they are immutable (more on this later).
local a = 5
local b = a
b = 6
print(a) -- prints 5
print(b) -- prints 6
In the above example, a
is of type number
which is a value type. The value of a
is copied to b
so changing b
does not affect a
.
local a = {5}
local b = a
b[1] = 6
print(a[1]) -- prints 6
print(b[1]) -- prints 6
In the above example, a
is a table type which is a reference type. The reference of a
is copied to b
so changing b
does affect a
.
In addition though, there are immutable references. Their contained values can never change. These behave like value types with respect to assignment (=
). The string
type is an example of an immutable reference type.
local a = "hello"
local b = a
b = "world"
print(a) -- prints "hello"
print(b) -- prints "world"
Note that all value types are also immutable. The only other immutable references in Luau are frozen tables. These are tables that have been made immutable with the table.freeze
function. We will talk about them more in the table section.
Tables are the do-all data structure in Lua. While there are a few more options in Luau, tables remain super important so it's important to understand them well!
Tables are associative arrays (AKA dictionaries/maps) and can be indexed with strings, numbers, or any other Luau value except nil
!
To create an empty table:
local myTable = {}
To set or modify values in a table:
myTable.name = "Alice"
myTable.age = 25
To create a table with some initial values:
local myTable = {name = "Alice", age = 25}
To access a value in a table:
print(myTable.name) -- prints "Alice"
print(myTable.age) -- prints 25
To explicitly define the type of the above table:
local myTable : {name: string, age: number} = {name = "Alice", age = 25}
To delete a key from a table:
myTable.name = nil
To check if a key exists in a table:
if myTable.name != nil then
print("name exists")
else
print("name does not exist")
end
Luau contains a set of built in functions for using tables as arrays. The "array" portion of the table includes all the elements with continuous integer keys starting at 1. You can instantiate such an array with the following syntax:
local myArray = {"c", "o", "w"}
print(myArray[1] .. myArray[2] .. myArray[3]) -- prints "cow"
Luau comes with a bunch of useful functions for manipulating tables as arrays. For example:
To add an element to the end of an array:
table.insert(myArray, "s")
print(myArray[1] .. myArray[2] .. myArray[3] .. myArray[4]) -- prints "cows"
To remove an element from an array:
table.remove(myArray, 4)
print(myArray[4]) -- prints nothing
Luau tables have no order however we can at least iterate over the array portion of a table in the expected order. To "sort" a table, we can use the sort
function which sorts the values in the array portion of the table (changing their indices).
local myArray = {"w", "o", "c"}
print(myArray[1] .. myArray[2] .. myArray[3]) -- prints "woc"
sort(myArray)
print(myArray[1] .. myArray[2] .. myArray[3]) -- prints "cow"
For more details on sort, see this article in the Programming in Lua book.
Using tables as arrays has one drawback. Tables can not hold nil
values as this simply deletes the key so arrays can not hold nil
values as this would break the continuous integer key requirement. To work around this, you can use the getn
and setn
functions. See this article in the Programming in Lua book for more information.
In Lua, iterators were required to for
loop over element in tables. To learn more about iterators, see the article on this topic in the Programming in Lua book. In Luau we can iterator over table key value pairs directly:
local myTable = {name = "Alice", age = 25}
for key, value in myTable do
print(key, value)
end
-- prints "name Alice" followed by "age 25"
Iteration order goes through continous numeric keys starting at 1 first (i.e. 1...#myTable), and then through the remaining elements with no guarantee on order. This is the same behavior as iterating with pairs
. Modifying table entries for any other keys besides the current one in the iterating results in undefined behavior.
To iterate over only the numeric keys of a table, you can use the ipairs
function:
local myTable = {"a", "b", "c", cat = "meow"}
for i, value in ipairs(myTable) do
print(i, value)
end
-- prints "1 a" followed by "2 b" followed by "3 c"
In the above examples, we used built in Luau syntax that automatically defines literal numeric and string keys. However, you can use any Luau value as a key in a table including values contained in variables. The generic syntax for accessing a value in a table is myTable[key]
where key
is some luau value.
local myTable = {}
local key = "name"
myTable[key] = "Alice"
print(myTable[key]) -- prints "Alice"
print(myTable.name) -- prints "Alice"
myTable["age"] = 25
print(myTable["age"]) -- prints 25
print(myTable.age) -- prints 25
You can even use other tables as table keys
local someTable = {}
local someOtherTable = {}
local myTable = {}
myTable[someTable] = "hello"
myTable[someOtherTable] = "world"
print(myTable[someTable]) -- prints "hello"
print(myTable[someOtherTable]) -- prints "world"
You can also use this syntax when constructing tables
local nameKey = "name"
local ageKey = "age"
local myTable = {
[nameKey] = "Alice",
[ageKey] = 25
}
print(myTable.name, myTable.age) -- prints "Alice 25"
Tables are often used as objects in Lua. A common pattern is to create an explicitly typed table to representy our object and create a set of functions that operate on that table.
type Person = {
name: string,
age: number
}
local function Person_create(name : string, age : number) : Person
return {name = name, age = age}
end
local function Person_greet(person : Person) : string
return "Yo wassup, " .. person.name
end
local function Person_growOlder(person : Person) : Person
return {name = person.name, age = person.age + 1}
end
You can also use metatables to create objects with methods built into them that look more like traditional object oriented programming. It is this author's opinion that this pattern should be avoided. This pattern is covered in the advanced section as it is apparently many folks do not share this author's opinion.
Metatables allow you to override certain behavior in tables. Metatables are used extensively by other libraries often to create more "convenient" interfaces. It is this author's opinions that metatables should be avoided perhaps the one exception of making container classes. Metatables are covered in the advanced section.
Here are some common gotchas when working with tables in Lua. Pay careful attention especially if you are coming from another language:
- Tables as arrays are 1-indexed. The first element of an array is at index 1, not 0. There is nothing preventing you from using 0 as an index, but the built-in functions for working with tables as arrays will not work as expected.
- Tables as arrays can still hold non-continuous-numeric keys. These will simply not be part of the "array" insofar as the built-in array manipulation functions are concerned. For example if you have
t = {1, 2, 3, [5] = 5}
the length#t
will be3
. - the
#
operator only returns the length of the array portion of the table. It will not return the total number of elements in the table!!!!!! To get the total number of elements in a table, you must iterate over the table and count them yourself π. - Using
ipairs
to iterate over a table as an array will stop at the first non-continuous-numeric key. - A
nil
value to a key in a table is equivalent to that key not existing in the table. To delete a key from a table, you can assignnil
to it. - Tables have no order to them!!!! To create an ordered set, you can use tables as arrays and sort them with the
sort
function. - Tables are passed by reference. When you pass a table to a function, you are passing a reference to the table, not a copy of the table. This means that if you modify the table in the function, the changes will be reflected outside of the function.
- Luau table types take a bit to get use to as the static type checker will do it's best to update the type as you add and remove items from the table. Understanding how the typechecker works for tables will eliminate a lot of potential bugs so it's worth getting use to!!
Similar to Javascript, functions are first class objects in Luau. This means that functions can be assigned to variables, passed as arguments to other functions, and returned from other functions. This is a very powerful tool that you should get use
function objects can be created using the lambda syntax:
local add = function(arg1 : number, arg2 : number)
return arg1 + arg2
end
print(tostring(add(1,2))) -- prints "3"
Once created, a function object can be used like any other value:
-- assign to another variable
local myFunction = add
print(tostring(myFunction(1,2))) -- prints "3"
-- change it to something else
myfunction = function(arg1 : number, arg2 : number)
return arg1 - arg2
end
print(tostring(myFunction(1,2))) -- prints "-1"
The perhaps more familiar function declaration syntax used earlier is in fact shorthand for the above. The below definiton is equivalent to the above:
local function add(arg1 : number, arg2 : number)
return arg1 + arg2
end
One of the main uses of functions as first class objects is to pass them as arguments to other functions. This is a common pattern in Luau. For example, the table.sort
function takes an optional comparison function as an argument:
local myArray = {3, 1, 2}
table.sort(myArray, function(a, b) return a < b end) -- by default, uses `<` as comparison, so we use `>` here
print(myArray[1], myArray[2], myArray[3]) -- prints "3 2 1"
You will see this pattern all over the place in the Roblox API which uses events e.g.
local Players = game:GetService("Players")
Players.PlayerAdded:Connect(function(player)
print(player.Name .. " joined the game!")
end)
Luau ships with several built in libraries for common operations. Some have already been covered and some more useful ones are covered here. For a complete list, see lua.org/library.
The io
provides functions for reading and writing files. These methods are usually not permitted when using Luau as an embedding scripting language for security reasons and this is the case for Roblox. The Programming in Lua book has a good section on the io
library if you are interested in learning more about it.
Luau is intended for use as an embedded language. That's why if you've ever read the Lua manual you'll notice that the majority is dedicated to the C API for embedding Lua in your program (yes, I linked the 5.1 Lua manual... we still need to write one for Luau...)
In order for Luau to be useful as an embedded language, it must be able to interact with the data types in the program it is embedded in. Examples of Roblox data types are things like Instance
and Player
. These data types are called "userdata" types. These are not to be confused with table types which can have new items added and removed. Userdata types have static definitions.
Instance myInstance = workspace.FindFirstChild("bearModel")
myInstance.Name = "bear" -- `Name` is a property of the `Instance` datatype so this is valid
myInstance.location = "Canada" -- `location` is not a property of myInstance, this is not valid!
Roblox data types are all documented on the very useful Roblox Creator Docs site.
The Luau vector type represents a 3D vector. This is in fact the same type as the differently named Vector3
type in Roblox although currently the type checker thinks they are different (we'll fix it soon). You can use either the bulit in vector library or the Roblox API variants. The vector library variants are more portable!
The vector type is either 3 or 4 dimensions (x,y,z + w) depending on what the compile time LUA_VECTOR_SIZE
flag is set to. 3 component vectors work like you would expect representing x y and z components. Roblox is configered to use 3 component vectors. 4 component vectors are needed for affine transformations which allow for things like translations to be represented as a matrices. This is super duper useful for computer graphics and much less useful for most games on Roblox. 4 component vectors are not useable on Roblox and thus are not covered in this guide.
To learn more about the vector library, see the Luau Vector Library page.
The buffer is a fixed size mutable (as in you can modify it's contents) block of memory. All buffer operations are done through the built in buffer
library. Buffers are useful in applications where bandwidth or performance is critical. Otherwise consider using tables as arrays instead. Note that all buffer library operations convert the contents to and from the number
or string
type but the internal representation is still a byte array.
To learn more about the buffer library, see the Luau Buffer Library page.
The thread
type are used to represent a coroutine. These can be manipulated with the builtin coroutine library. The Programming in Lua book has a good section on coroutines if you are interested in learning more about them.
If you are working in Roblox, coroutines are often managed by the task library. These methods create coroutines that are managed by the Roblox engine's TaskSchedule such that should not (ever) manually coroutine.yield
and coroutine.resume
them. The thread
type and the coroutine
type mentioned on the Roblox API website are the same.
Luau is an actively developed language and new features are being added all the time. You can propose and discuss new features on the Luau RFCs github page.
Luau is open source and has a number super useful community supported tooling!
<TODO lune, wally/rotriever/npm/thenewthing/robloxsnewthing, new wally, aftman/forman/thenewthing, linter, useful libraries,>
Please see the Third Party Tools for using Luau tooling with Roblox.
All of Roblox's UI instances can be managed with Luau code allowing you to generate UI entirely from Luau. This is the simplest create UI in Roblox. Many creators will author their UI in the Roblox Studio editor and then add Luau code to manage the interactions. You can also create your UI entirely from Luau code.
Using native UI is the simplest way to create UI in Roblox. You may find that you will start running into limitations as your UI needs become mor demanding and complex. Thankfully, there are UI libraries that can help you with this!
Fusion is a declarative UI, state management and animation library for Roblox. This is an awesome choice for building UI in Roblox and relatively easy to pick up. Please see https://elttob.uk/Fusion/0.2/ for more information on Fusion.
React-lua is a port of React to Luau. React is an industry standard for many web and mobile apps these days and modern React has never been easier to use. Nonetheless the learning curve is steeper than using native UI or fusion. If you are new to programming avoid react-lua. Please see my react-lua tutorial :).
Metatables are a powerful feature in Luau allowing you to modify the behavior of tables. It is this author's opinion that metatables just make your code harder to read and understand in most cases and should be avoided. Still, they are widely used so you should be aware of them. This section of the guide is very incomplete. I may finish it in the future or not. Please see the Programming in Lua book for more information on metatables for now.
Luau doesn't exactly have built in support for typing tables with metatables. The metatables is just treated like any other keyed entry in the table if I'm not mistaken. This makes typing tables with metatables a bit of a pain which is another reason to avoid metatables. I will write a proper guide on how to type tables with metatabels as soon as I figure out how to do it myself.
Since Object Oriented Programming with super popular for a while all the non object oriented languages added pseudo support for object oriented programming and Lua was no exception. Pseudo object oriented programming in Lua is done with metatables and it is just as awful as when they tried to do it in Javascript before adding native suipport. Please avoid as much as possible with perhaps the one exception of making container classes (e.g. queue, stack, etc).
If you really must, please see the Programming in Lua book for more information on object oriented programming in Lua. OOP is used extensively in other Lua libraries so you probably will want to know it at some point :|.
The one thing you should be aware is that Luau (and Lua) has built in special syntax to support "member functions"
local Booper = {}
function Booper.boop()
print("boop!")
end
Booper:boop() -- prints "boop!"
To make these operations stateful:
local BoopCounter = {}
function BoopCounter.boop(self)
self.count = self.count + 1
end
local myBoopCounter = {count = 0}
print (myBoopCounter.count) -- prints 0
BoopCounter.boop(myBoopCounter)
print (myBoopCounter.count) -- prints 1
Furthermore you can use the :
syntax to implicitly pass the table as the first argument to the function. This is just syntactic sugar and is equivalent to the above.
function BoopCounter:boop()
self.count = self.count + 1
end
If the above looks weird and confusing perhaps becaues you are familiar with objected oriented programming or perhaps just because it's weird and confusion, then you are absolutely right. Luau does not natively support object oriented programming beyond the syntactic sugar above and instead relies on the prototype pattern to implement them. The prototype pattern is in this author's opinion a half baked pattern to implement OOP in functional languages that should be avoided. IT is like a blobby amalgamation of the class definiton and the object itself which has a method to poop out mini blobs tied to the parent prototype blob and is a confusing mess.
You can "freeze" a table to make it read-only.
local π₯Ά = {1, 2, 3}
table.freeze(π₯Ά)
π₯Ά[1] = 4 -- this will raise an error
See the table library for more information on frozen tables.
Luau is a managed language meaning it's objects are automatically "garbage collected" and it's memory freed when there are no longer any "strong references" to that object. In general, anytime you assign an object to a variable or table, it becomes a strong reference to that object. A "weak reference" is a reference to an object that does not prevent the object from being garbage collected. Weak references are only available as keys and values in Luau tables. To create a table with weak keys, set the __mode
field of the metatable to "k"
. To create a table with weak values, set the __mode
field of the metatable to "v"
. To create a table with weak keys and values, set the __mode
field of the metatable to "kv"
.
a = {}
b = {}
setmetatable(a, b)
b.__mode = "k" -- table `a` has weak keys now
local key = {}
a[key] = 1
key = {} -- this replaces the only strong reference to the `{}` object created earlier, there is still a weak reference in the table
a[key] = 2
collectgarbage() -- force a garbage collection cycle
-- this will only print 2, because the 1 key was garbage collected and the corresponding value gets removed from the table
for k, v in pairs(a) do
print(v)
end
In the example above, the entry created with a[key] = 1
gets removed when the only strong reference to the key object is removed. A similar thing happens with weak values.