Data Types
Learning Objectives
By the end of this lesson, you will be able to:
- Identify and use all fundamental Luau data types
- Understand the difference between
nilandfalse - Work with numbers in various formats (decimal, hex, binary)
- Create and manipulate strings using multiple syntaxes
- Use string interpolation for dynamic text
- Understand how types differ from JavaScript and After Effects
AE/JS Syntax Comparison
Before diving in, here's how Luau data types compare to what you may already know:
| Concept | After Effects Expression | JavaScript | Luau |
|---|---|---|---|
| No value | N/A | null / undefined | nil |
| Boolean | true, false | true, false | true, false |
| Number | 42, 3.14 | 42, 3.14 | 42, 3.14 |
| Text | "hello" | "hello" or 'hello' | "hello" or 'hello' |
| Array | [1, 2, 3] | [1, 2, 3] | {1, 2, 3} |
| Object | N/A | {key: value} | {key = value} |
| String join | "Hello " + name | "Hello " + name | "Hello " .. name |
| Template | N/A | `Hello ${name}` | `Hello {name}` |
- No
nullvsundefined: Luau has onlynil - No separate integer type: All numbers are double-precision floats
- Curly braces for arrays: Use
{}not[] - Different string concatenation: Use
..not+ - Simpler interpolation: Use
{var}not${var}
The nil Type
nil represents the absence of a value. It's Luau's way of saying "nothing" or "not set."
local uninitializedVariable -- This is nil
local explicitNil = nil -- Explicitly set to nil
-- Check if something exists
if uninitializedVariable == nil then
print("Variable has no value")
end
JavaScript Comparison
// JavaScript has TWO "nothing" types - confusing!
let a; // undefined
let b = null; // null
a === undefined // true
b === null // true
a == b // true (loose equality)
a === b // false (strict equality)
-- Luau has ONE "nothing" type - simple!
local a -- nil
local b = nil -- nil
a == nil -- true
b == nil -- true
a == b -- true (always straightforward)
Why nil Matters in Rive
In Rive Node Scripts, nil is commonly used with:
- Uninitialized values: Before
init()runs, fields may benil - Optional parameters: Functions can have optional arguments
- Missing table keys: Accessing a non-existent key returns
nil - late() initializer: Returns
nilbefore the engine provides the value
export type MyScript = {
health: number?, -- The ? means this can be nil
}
function init(self: MyScript): boolean
-- health might be nil if not set
if self.health == nil then
self.health = 100
end
return true
end
The boolean Type
Booleans represent logical values: true or false.
local isActive = true
local hasCollided = false
-- From comparisons
local isPositive = 5 > 0 -- true
local isEmpty = #"hello" == 0 -- false
Truthy and Falsy Values
In Luau, only false and nil are falsy. Everything else is truthy!
-- These are ALL truthy in Luau:
if 0 then print("0 is truthy!") end -- PRINTS!
if "" then print("empty string is truthy!") end -- PRINTS!
if {} then print("empty table is truthy!") end -- PRINTS!
JavaScript Comparison
// JavaScript falsy values (6 total):
if (false) {} // falsy
if (null) {} // falsy
if (undefined) {} // falsy
if (0) {} // falsy ← Different from Luau!
if ("") {} // falsy ← Different from Luau!
if (NaN) {} // falsy
// Common JS pattern that DOESN'T work the same in Luau:
let score = 0;
if (!score) {
console.log("No score"); // This prints in JS!
}
-- Luau falsy values (2 only):
if false then end -- falsy
if nil then end -- falsy
-- This behaves DIFFERENTLY than JS:
local score = 0
if not score then
print("No score") -- DOES NOT PRINT! 0 is truthy!
end
-- Correct Luau way:
if score == 0 then
print("Score is zero")
end
The number Type
All numbers in Luau are double-precision floating-point (64-bit). There's no separate integer type.
Number Literals
-- Standard decimal
local count = 42
local price = 19.99
local negative = -100
-- Hexadecimal (base 16) - useful for colors
local red = 0xFF0000 -- 16711680
local green = 0x00FF00 -- 65280
local blue = 0x0000FF -- 255
-- Binary (base 2) - useful for bit flags
local flags = 0b1010 -- 10
local mask = 0b11110000 -- 240
-- Underscores for readability (large numbers)
local million = 1_000_000 -- 1000000
local precise = 3.141_592_653 -- 3.141592653
JavaScript Comparison
// JavaScript numbers are similar
let count = 42;
let price = 19.99;
let hex = 0xFF0000;
let binary = 0b1010;
let bigNumber = 1_000_000; // ES2021+
// JavaScript also has BigInt (Luau doesn't)
let huge = 9007199254740993n; // No equivalent in Luau
Precision Note
Since all numbers are floats, very large integers may lose precision:
local big = 9007199254740992 -- Safe max integer
local tooBig = 9007199254740993 -- May lose precision!
Special Number Values
local infinity = math.huge -- Positive infinity
local negInfinity = -math.huge -- Negative infinity
local notANumber = 0/0 -- NaN (not a number)
-- Check for special values
local x = 0/0
if x ~= x then
print("x is NaN") -- NaN is the only value that doesn't equal itself!
end
The string Type
Strings are sequences of characters for text data.
String Syntax Options
Luau offers three ways to create strings:
-- Single quotes
local single = 'Hello, World!'
-- Double quotes (most common)
local double = "Hello, World!"
-- Multi-line strings with [[]]
local multi = [[
This is a
multi-line string.
It preserves line breaks!
]]
Escape Sequences
local newline = "Line 1\nLine 2" -- \n = new line
local tab = "Column1\tColumn2" -- \t = tab
local quote = "He said \"Hello!\"" -- \" = literal quote
local backslash = "Path\\to\\file" -- \\ = literal backslash
String Concatenation
.. not +Luau uses .. for string concatenation, NOT + like JavaScript!
-- Luau concatenation
local first = "Hello"
local second = "World"
local greeting = first .. " " .. second -- "Hello World"
-- Numbers are automatically converted
local score = 100
local message = "Score: " .. score -- "Score: 100"
// JavaScript uses +
let greeting = first + " " + second;
let message = "Score: " + score;
String Interpolation (Backticks)
Luau supports string interpolation with backticks - this is the cleanest way to build dynamic strings:
local name = "Player"
local level = 42
local health = 85.5
-- Old way with concatenation (messy)
local old = "Welcome, " .. name .. "! Level " .. level .. " - HP: " .. health
-- New way with interpolation (clean)
local new = `Welcome, {name}! Level {level} - HP: {health}`
-- Both produce: "Welcome, Player! Level 42 - HP: 85.5"
JavaScript Comparison
// JavaScript template literals use ${}
let message = `Welcome, ${name}! Level ${level} - HP: ${health}`;
-- Luau interpolation uses {} (no $ needed)
local message = `Welcome, {name}! Level {level} - HP: {health}`
String Length
local text = "Hello"
local length = #text -- 5
-- Works with variables too
local message = "Rive is awesome!"
print(`Message has {#message} characters`) -- "Message has 16 characters"
The table Type
Tables are Luau's only data structure - they serve as arrays, dictionaries, and objects.
-- Array-style (sequential integer keys)
local colors = {"red", "green", "blue"}
-- Dictionary-style (named keys)
local player = {
name = "Hero",
health = 100,
position = {x = 0, y = 0}
}
-- Mixed (possible but not recommended)
local mixed = {
"first", -- Index 1
"second", -- Index 2
name = "mixed" -- Named key
}
Tables are covered in depth in the Tables lesson. This is just an overview of the type.
The function Type
Functions are first-class values in Luau - they can be stored in variables, passed as arguments, and returned from other functions.
-- Function stored in variable
local greet = function(name: string): string
return `Hello, {name}!`
end
-- Call it
local message = greet("World") -- "Hello, World!"
-- Type of function
print(type(greet)) -- "function"
Functions are covered in depth in the Functions lesson. This is just an overview of the type.
Type Checking with type()
The built-in type() function returns the type of any value as a string:
print(type(nil)) -- "nil"
print(type(true)) -- "boolean"
print(type(42)) -- "number"
print(type("hello")) -- "string"
print(type({})) -- "table"
print(type(function() end)) -- "function"
Runtime Type Guards
local function processValue(value: any)
if type(value) == "number" then
print(`Number: {value * 2}`)
elseif type(value) == "string" then
print(`String: {value:upper()}`)
elseif type(value) == "boolean" then
print(`Boolean: {if value then "yes" else "no"}`)
elseif type(value) == "nil" then
print("Got nil!")
else
print(`Unknown type: {type(value)}`)
end
end
Exercises
Exercise 1: Type Snapshot ⭐
Premise
When you bind data or debug scripts in Rive, you often need to confirm what type a value actually is. Using type() helps you catch mismatches early, before the wrong value is fed into a drawing or Input Path.
By the end of this exercise, you will define values of multiple types and print a compact verification summary.
Use Case
Imagine a script that reads data from different sources: a ViewModel value, a state flag, and a table of points for a path. You need to verify that each is the expected type before you build visuals. A quick type snapshot prevents debugging blind spots.
Example scenarios:
- Verifying ViewModel properties before use
- Checking whether a value is a table or a number
Setup
In Rive Editor:
-
Create the script:
- Assets panel →
+→ Script → Node Script - Name it
Exercise1_TypeSnapshot
- Assets panel →
-
Attach and run:
- Attach to any shape and press Play
Starter Code
--!strict
export type Exercise1 = {}
function init(self: Exercise1): boolean
-- TODO 1: Set isActive to true
local isActive = false
-- TODO 2: Set count to 42
local count = 0
-- TODO 3: Set label to "Score"
local label = ""
-- TODO 4: Set points to a table with 3 numbers
local points = {}
-- Keep this as nil on purpose
local maybeNil: any = nil
local callback = function() end
local summary = `{type(maybeNil)},{isActive},{count},{label},{#points},{type(callback)}`
print(`ANSWER: {summary}`)
return true
end
function draw(self: Exercise1, renderer: Renderer)
end
return function(): Node<Exercise1>
return {
init = init,
draw = draw,
}
end
Assignment
Complete these tasks:
- Set
isActivetotrue - Set
countto42 - Set
labelto"Score" - Set
pointsto a table with three numbers (length 3)
Expected Output
Your console output should display a comma-separated summary containing:
- The type of the nil variable
- The boolean value
- The numeric count
- The string label
- The length of the points table
- The type of the callback function
Verify Your Answer
Checklist
- All values match the requested types
- Table length is exactly 3
- Output matches the expected
ANSWER:line
Exercise 2: Truthiness Check ⭐
Premise
In Luau, only false and nil are falsy. That is a frequent source of bugs for developers coming from JavaScript, where 0 and empty strings are falsy.
By the end of this exercise, you will compute truthiness results using if in Luau.
Use Case
You are building a script that tests inputs like "opacity" and "label text." In JavaScript, an empty string or 0 would act false, but in Luau they do not. This check keeps you from disabling UI elements by mistake.
Example scenarios:
- Validating numeric inputs like 0
- Treating empty strings as present values
Setup
In Rive Editor:
-
Create the script:
- Assets panel →
+→ Script → Node Script - Name it
Exercise2_Truthiness
- Assets panel →
-
Attach and run:
- Attach to any shape and press Play
Starter Code
--!strict
export type Exercise2 = {}
function init(self: Exercise2): boolean
-- TODO 1: Use if-statements to set these truthiness results
local zeroTruthy = false
local emptyStringTruthy = false
local nilTruthy = true
print(`ANSWER: zero={zeroTruthy},empty={emptyStringTruthy},nil={nilTruthy}`)
return true
end
function draw(self: Exercise2, renderer: Renderer)
end
return function(): Node<Exercise2>
return {
init = init,
draw = draw,
}
end
Assignment
Complete these tasks:
- Set
zeroTruthyusingif 0 then ... - Set
emptyStringTruthyusingif "" then ... - Set
nilTruthyusingif nil then ...
Expected Output
Your console output should display three truthiness results:
zero=— whether 0 is truthy in Luauempty=— whether an empty string is truthy in Luaunil=— whether nil is truthy in Luau
Remember: Luau's truthiness rules differ from JavaScript!
Verify Your Answer
Checklist
- You used
ifchecks, not hard-coded strings -
zeroTruthyandemptyStringTruthyare true in Luau -
nilTruthyis false
Exercise 3: Label Builder ⭐⭐
Premise
Most Rive scripts build strings for labels, debug logs, or state banners. You need both concatenation (..) and interpolation (backticks) to format text cleanly.
By the end of this exercise, you will build the same label using concatenation and interpolation.
Use Case
You are building a scoreboard label that includes player name, level, and HP. The label is used in the UI and also logged in the Console for debugging. Creating it in two different ways shows you how to pick the most readable approach.
Example scenarios:
- HUD labels and status text
- Debug logs for values during animation
Setup
In Rive Editor:
-
Create the script:
- Assets panel →
+→ Script → Node Script - Name it
Exercise3_LabelBuilder
- Assets panel →
-
Attach and run:
- Attach to any shape and press Play
Starter Code
--!strict
export type Exercise3 = {}
function init(self: Exercise3): boolean
local name = "Nova"
local level = 7
local hp = 85
local maxHp = 100
-- TODO 1: Build concatLabel using ..
local concatLabel = ""
-- TODO 2: Build interpLabel using backticks
local interpLabel = ""
print(`ANSWER: {concatLabel} | {interpLabel}`)
return true
end
function draw(self: Exercise3, renderer: Renderer)
end
return function(): Node<Exercise3>
return {
init = init,
draw = draw,
}
end
Assignment
Complete these tasks:
- Build
concatLabelwith..using this format:Player Nova (Lv.7) - HP 85/100
- Build
interpLabelusing backticks with the same format
Expected Output
Your console output should display two identical labels separated by " | ":
- The first label built with concatenation (
..) - The second label built with interpolation (backticks)
Both labels should follow the format: Player [name] (Lv.[level]) - HP [hp]/[maxHp]
Verify Your Answer
Checklist
-
concatLabeluses..at least twice -
interpLabeluses backticks and{}placeholders - Both labels match exactly
Exercise 4: Number Formats ⭐⭐
Premise
Rive scripts use hex for colors, binary for flags, and underscores for readability. These formats are common in production scripts and should feel natural.
By the end of this exercise, you will assign values using hex, binary, and readable numeric formats and verify their decimal results.
Use Case
You are translating design specs into numbers: a hex color, a bit-flag mask, and a large time constant. Using the correct format keeps code readable and avoids mistakes when you work with artists or other engineers.
Example scenarios:
- Converting design colors into numeric values
- Packing multiple on/off flags into one number
Setup
In Rive Editor:
-
Create the script:
- Assets panel →
+→ Script → Node Script - Name it
Exercise4_NumberFormats
- Assets panel →
-
Attach and run:
- Attach to any shape and press Play
Starter Code
--!strict
export type Exercise4 = {}
function init(self: Exercise4): boolean
-- TODO 1: Set hexValue to 0xFF
local hexValue = 0
-- TODO 2: Set binaryValue to 0b1010
local binaryValue = 0
-- TODO 3: Set readableValue to 1_000_000
local readableValue = 0
-- TODO 4: sum should be hexValue + binaryValue
local sum = 0
print(`ANSWER: hex={hexValue},bin={binaryValue},sum={sum},readable={readableValue}`)
return true
end
function draw(self: Exercise4, renderer: Renderer)
end
return function(): Node<Exercise4>
return {
init = init,
draw = draw,
}
end
Assignment
Complete these tasks:
- Set
hexValueto0xFF(decimal 255) - Set
binaryValueto0b1010(decimal 10) - Set
readableValueto1_000_000 - Set
sumtohexValue + binaryValue
Expected Output
Your console output should display four values:
hex=— the decimal equivalent of 0xFFbin=— the decimal equivalent of 0b1010sum=— the sum of hex and binary valuesreadable=— the number with underscores as plain decimal
All values should be displayed as plain decimal numbers.
Verify Your Answer
Checklist
- Hex and binary values are written using 0x / 0b formats
- The readable number uses underscores
- sum is 265
Exercise 5: Nil Defaults ⭐⭐
Premise
In Rive scripts, values can be nil before you assign them (especially with late bindings). Safe defaults keep your script from crashing and make your UI predictable.
By the end of this exercise, you will use or and nil checks to produce safe defaults.
Use Case
A script reads a name and lives count from external data. If those values are not present yet, the UI should still show a fallback label and a default number of lives. This is common in early initialization frames.
Example scenarios:
- Optional ViewModel values
- Late-bound inputs that are not set yet
Setup
In Rive Editor:
-
Create the script:
- Assets panel →
+→ Script → Node Script - Name it
Exercise5_NilDefaults
- Assets panel →
-
Attach and run:
- Attach to any shape and press Play
Starter Code
--!strict
export type Exercise5 = {}
function init(self: Exercise5): boolean
local inputName: string? = nil
local inputLives: number? = nil
-- TODO 1: Use "or" to set displayName to "Guest" when inputName is nil
local displayName = ""
-- TODO 2: Use "or" to set lives to 3 when inputLives is nil
local lives = 0
-- TODO 3: hasName should be true only when inputName is not nil
local hasName = true
print(`ANSWER: name={displayName},lives={lives},hasName={hasName}`)
return true
end
function draw(self: Exercise5, renderer: Renderer)
end
return function(): Node<Exercise5>
return {
init = init,
draw = draw,
}
end
Assignment
Complete these tasks:
- Set
displayNametoinputName or "Guest" - Set
livestoinputLives or 3 - Set
hasNameusing an explicit nil check
Expected Output
Your console output should display three values:
name=— the display name (either the input or the default)lives=— the lives count (either the input or the default)hasName=— whether the original input name was provided (not nil)
The defaults should be used since both inputs are nil.
Verify Your Answer
Checklist
- Defaults use
orinstead of hard-coded replacements later -
hasNamechecks against nil explicitly - Output matches the expected line
Knowledge Check
Common Mistakes
- Assuming 0 is falsy - In Luau, 0 is truthy! Use explicit
== 0checks - Using + for string concatenation - Use
..instead:"Hello" .. "World" - Using $ in interpolation - Use
{var}not${var}in backtick strings - Forgetting nil checks - Always verify values exist before using them
- Confusing nil with false -
nil == falseis FALSE! They are different values - Using [] for arrays - Use
{}:{1, 2, 3}not[1, 2, 3]
Quick Reference
-- Types
nil -- No value
true, false -- Booleans
42, 3.14, 0xFF -- Numbers (all double-precision)
"hello", 'world' -- Strings
{1, 2, 3} -- Table (array)
{key = "value"} -- Table (dictionary)
function() end -- Function
-- Type checking
type(value) -- Returns "nil", "boolean", "number", "string", "table", "function"
-- String operations
"Hello" .. " World" -- Concatenation: "Hello World"
#"Hello" -- Length: 5
`Value: {var}` -- Interpolation
-- nil handling
value or default -- Default value pattern
if x ~= nil then -- Explicit nil check
-- Number formats
42 -- Decimal
0xFF -- Hexadecimal (255)
0b1010 -- Binary (10)
1_000_000 -- Readable (1000000)
Next Steps
- Continue to Operators
- Need a refresher? Review Quick Reference