Variables & Scope
Local vs. Global Variables
message = "I am global"
local_var = "Ruby locals are always local"
puts message message = "I am global" -- WARNING: global by default!
local greeting = "I am local"
print(message)
print(greeting) In Lua, variables are global by default. You must use the
local keyword to create a local variable. This is the opposite of Ruby, where all variables are local unless you use a $ prefix for globals. Forgetting local is the most common Lua bug.Multiple Assignment
first, second, third = 1, 2, 3
puts "#{first}, #{second}, #{third}" local first, second, third = 1, 2, 3
print(first, second, third) Lua supports multiple assignment natively. If there are more variables than values, extras get
nil. If there are more values than variables, extras are discarded.Swap Values
first, second = 1, 2
first, second = second, first
puts "#{first}, #{second}" local first, second = 1, 2
first, second = second, first
print(first, second) Multiple assignment makes swapping values without a temporary variable idiomatic in Lua, just as in Ruby.
nil β Absence of a Value
value = nil
puts value.nil?
puts value.inspect local value = nil
print(value == nil) -- true
print(type(value)) -- "nil" Like Ruby's
nil, Lua's nil represents the absence of a value. In Lua, setting a table key to nil deletes that key from the table. There is no .nil? method β use == nil or type(x) == "nil".Boolean Logic
puts true && false
puts true || false
puts !true
puts nil || "default" print(true and false) -- false
print(true or false) -- true
print(not true) -- false
print(nil or "default") -- "default" Lua uses words:
and, or, not instead of &&, ||, !. Crucially, and and or return one of their operands, not a boolean β just like Ruby's && and ||.No ++ or += Operators
count = 0
count += 1
count += 5
puts count local count = 0
count = count + 1
count = count + 5
print(count) Lua has no
++, --, +=, or -= operators. All increments are spelled out explicitly. This is intentional β Lua's designers valued simplicity over syntactic sugar.Types & Values
Checking Types with type()
puts 42.class
puts "hello".class
puts true.class
puts nil.class print(type(42)) -- "number"
print(type("hello")) -- "string"
print(type(true)) -- "boolean"
print(type(nil)) -- "nil"
print(type({})) -- "table"
print(type(print)) -- "function" Lua has eight basic types:
nil, boolean, number, string, function, table, userdata, and thread. The type() function returns a string name, unlike Ruby's .class which returns a class object.Numbers β Integer and Float
puts 42.class
puts 3.14.class
puts (10 / 3)
puts (10.0 / 3) print(type(42)) -- "number"
print(type(3.14)) -- "number"
print(10 // 3) -- 3 (floor division)
print(10 / 3) -- 3.3333... (always float)
print(math.type(42)) -- "integer"
print(math.type(3.14)) -- "float" In Lua 5.3+, there are two number subtypes β integer and float β but both share the single
"number" type. The / operator always produces a float, even with two integers. Use // for integer (floor) division. This is the opposite of Ruby where 10 / 3 truncates to 3.Falsy Values
# In Ruby, only nil and false are falsy
puts "falsy" if !nil
puts "falsy" if !false
# 0 and "" are TRUTHY in Ruby
puts "truthy" if 0
puts "truthy" if "" -- In Lua, only nil and false are falsy
if not nil then print("nil is falsy") end
if not false then print("false is falsy") end
-- 0 and "" are TRUTHY in Lua (same as Ruby!)
if 0 then print("0 is truthy") end
if "" then print('"" is truthy') end Lua and Ruby agree: only
nil and false are falsy. Zero and empty string are truthy in both languages. This differs from Python, JavaScript, and C.String / Number Coercion
begin
puts "10" + 5 # TypeError!
rescue TypeError => error
puts "TypeError: #{error.message}"
end
puts "10".to_i + 5
puts 42.to_s + " bottles" -- Lua coerces strings to numbers automatically in arithmetic
print("10" + 5) -- 15
print("3.14" * 2) -- 6.28
-- And numbers to strings in concatenation
print(42 .. " bottles") -- "42 bottles" Lua automatically coerces strings to numbers in arithmetic contexts and numbers to strings in concatenation contexts. Ruby does not β it raises a
TypeError. This makes Lua more permissive but also more surprising.Inequality Operator
puts 1 != 2
puts "a" != "b" print(1 ~= 2) -- true
print("a" ~= "b") -- true Lua uses
~= for "not equal", while Ruby uses !=. This is one of the first things Rubyists accidentally write wrong.Strings
String Concatenation
greeting = "Hello" + ", " + "world!"
puts greeting local greeting = "Hello" .. ", " .. "world!"
print(greeting) Lua uses
.. (two dots) for string concatenation, not +. Using + on strings triggers the number-coercion behavior and produces an error if the strings are not numeric.String Length
puts "hello".length
puts "hello".size print(#"hello") -- 5
local word = "hello"
print(#word) -- 5
print(string.len("hello")) -- 5 The
# operator returns the byte length of a string (not the character count for multi-byte strings). string.len() is equivalent. Unlike Ruby's .length, this is an operator, not a method.String Formatting
name = "Alice"
age = 30
puts "Name: #{name}, Age: #{age}"
puts "Pi is %.4f" % Math::PI local name = "Alice"
local age = 30
print(string.format("Name: %s, Age: %d", name, age))
print(string.format("Pi is %.4f", math.pi)) Lua has no string interpolation. Use
string.format() with C-style format specifiers: %s for strings, %d for integers, %f for floats. This is similar to Ruby's sprintf or % operator.Case Conversion
puts "hello".upcase
puts "WORLD".downcase print(string.upper("hello")) -- "HELLO"
print(string.lower("WORLD")) -- "world" String functions in Lua live in the
string library. They can also be called as methods using the colon syntax: ("hello"):upper(). This works because all Lua strings share a metatable that indexes into the string library.Substrings
puts "hello world"[0, 5]
puts "hello world"[6..] print(string.sub("hello world", 1, 5)) -- "hello"
print(string.sub("hello world", 7)) -- "world"
print(string.sub("hello world", -5)) -- "world" string.sub(str, start, end) extracts a substring. Indices are 1-based and inclusive on both ends. Negative indices count from the end: -1 is the last character. Ruby's slice notation is more flexible, but string.sub covers the common cases.Finding in Strings
puts "hello world".include?("world")
puts "hello world".index("world") local start_pos, end_pos = string.find("hello world", "world")
print(start_pos, end_pos) -- 7 11
print(start_pos ~= nil) -- true (found) string.find returns the start and end positions of a match (1-based), or nil if not found. To check existence, test whether the result is not nil. By default it treats the pattern as a Lua pattern (like a limited regex). Pass true as a fourth argument for a plain string search.String Replacement
puts "hello world".gsub("world", "Lua")
puts "aabbcc".gsub(/[abc]/, "x") print(string.gsub("hello world", "world", "Lua"))
-- "hello Lua" 1
print(string.gsub("aabbcc", "[abc]", "x"))
-- "xxxxxx" 6 string.gsub returns two values: the modified string and the number of substitutions made. Lua patterns are not full regexes β they use %d, %a, %s, etc. instead of \d, \w, \s.String Repetition
puts "ha" * 3
puts "-" * 20 print(string.rep("ha", 3)) -- "hahaha"
print(string.rep("-", 20)) -- "--------------------"
print(string.rep("ab", 3, ",")) -- "ab,ab,ab" string.rep(str, n) repeats a string n times. The optional third argument is a separator inserted between repetitions β a feature Ruby's * operator does not have.Tables
Tables as Arrays (1-based!)
numbers = [10, 20, 30]
puts numbers[0] # 10 β 0-based
puts numbers[1] # 20
puts numbers.length local numbers = {10, 20, 30}
print(numbers[1]) -- 10 (1-based!)
print(numbers[2]) -- 20
print(#numbers) -- 3 Lua tables use 1-based indexing by default. This is the most jarring difference for every programmer coming from Ruby, Python, or JavaScript.
numbers[0] is nil β not an error, and not the first element.Inserting and Removing
items = [1, 2, 3]
items.push(4)
items.unshift(0)
items.pop
p items local items = {1, 2, 3}
table.insert(items, 4) -- append
table.insert(items, 1, 0) -- insert at position 1
table.remove(items) -- remove last
for i, value in ipairs(items) do
io.write(value .. " ")
end
print() table.insert(t, val) appends to the end. table.insert(t, pos, val) inserts at a position, shifting other elements right. table.remove(t) removes the last element; table.remove(t, pos) removes a specific position.Sorting
numbers = [3, 1, 4, 1, 5, 9]
numbers.sort!
p numbers
words = ["banana", "apple", "cherry"]
words.sort_by!(&:length)
p words local numbers = {3, 1, 4, 1, 5, 9}
table.sort(numbers)
for _, v in ipairs(numbers) do io.write(v .. " ") end
print()
local words = {"banana", "apple", "cherry"}
table.sort(words, function(a, b) return #a < #b end)
for _, w in ipairs(words) do io.write(w .. " ") end
print() table.sort sorts in-place. An optional comparison function takes two elements and returns true if the first should come before the second β the same convention as Ruby's sort_by.Tables as Dictionaries
person = { name: "Alice", age: 30 }
puts person[:name]
puts person[:age] local person = { name = "Alice", age = 30 }
print(person.name) -- "Alice"
print(person["age"]) -- 30 The same table type serves as both array and dictionary. String keys can be accessed with dot notation (
person.name) or bracket notation (person["name"]). Dot notation is just syntactic sugar for bracket notation with a string literal.Setting nil Deletes a Key
config = { debug: true, verbose: false }
config.delete(:debug)
p config local config = { debug = true, verbose = false }
config.debug = nil -- deletes the key
for key, value in pairs(config) do
print(key, value)
end Assigning
nil to a table key removes that key entirely. There is no separate delete method β t.key = nil is the idiom. This also means you cannot store nil as a value in a table.Iterating β pairs vs. ipairs
# Ruby's each works on both arrays and hashes
[10, 20, 30].each { |v| puts v }
{ a: 1, b: 2 }.each { |k, v| puts "#{k}: #{v}" } -- ipairs: iterate array part (stops at first nil)
for index, value in ipairs({10, 20, 30}) do
print(index, value)
end
-- pairs: iterate ALL keys (unordered)
for key, value in pairs({a = 1, b = 2}) do
print(key, value)
end ipairs iterates the integer-keyed sequence starting at 1, stopping at the first nil. pairs iterates all key-value pairs in unspecified order. For mixed tables, you may need both. There is no guarantee on the iteration order of pairs.Joining a Table into a String
words = ["one", "two", "three"]
puts words.join(", ") local words = {"one", "two", "three"}
print(table.concat(words, ", ")) table.concat joins the sequence part of a table into a string with a separator. It only works on tables with consecutive integer keys starting at 1. It is much faster than building a string by concatenation in a loop.Nested Tables
matrix = [[1, 2], [3, 4]]
puts matrix[0][1]
person = { name: "Alice", address: { city: "NYC" } }
puts person[:address][:city] local matrix = {{1, 2}, {3, 4}}
print(matrix[1][2]) -- 2
local person = { name = "Alice", address = { city = "NYC" } }
print(person.address.city) -- "NYC" Tables nest naturally. Accessing a missing key returns
nil rather than raising an error, but chaining through a nil value β like person.address.city when address is nil β will raise a "attempt to index a nil value" error.Control Flow
if / elseif / else
score = 85
if score >= 90
puts "A"
elsif score >= 80
puts "B"
else
puts "C"
end local score = 85
if score >= 90 then
print("A")
elseif score >= 80 then
print("B")
else
print("C")
end Lua uses
elseif (one word, no space) where Ruby uses elsif. The condition is followed by then (except when followed by a newline, where it is optional). Blocks end with end.Ternary Idiom with and/or
value = 42
result = value > 0 ? "positive" : "non-positive"
puts result local value = 42
local result = value > 0 and "positive" or "non-positive"
print(result) Lua has no ternary operator (
?:). The and/or idiom works because and returns its second operand if the first is truthy, and or returns its second if the first is falsy. Caveat: this breaks if the "true" branch value is false or nil.while Loop
count = 1
while count <= 5
puts count
count += 1
end local count = 1
while count <= 5 do
print(count)
count = count + 1
end Lua's
while loop works exactly like Ruby's. The body is wrapped in do ... end instead of ending with end alone.repeat / until
count = 1
begin
puts count
count += 1
end while count <= 5 local count = 1
repeat
print(count)
count = count + 1
until count > 5 repeat / until is Lua's do-while equivalent. The body always runs at least once, and the condition is checked after the body. Note that locals declared inside the body are visible in the until condition β a subtle but useful difference from while.Numeric for Loop
(1..5).each { |i| puts i }
(0..20).step(5) { |i| puts i } for i = 1, 5 do
print(i)
end
for i = 0, 20, 5 do -- start, stop, step
print(i)
end The numeric
for loop takes a start value, a limit, and an optional step (default 1). It is inclusive on both ends. The loop variable is automatically local β there is no need to declare it with local.Generic for Loop
["apple", "banana", "cherry"].each_with_index do |fruit, index|
puts "#{index}: #{fruit}"
end local fruits = {"apple", "banana", "cherry"}
for index, fruit in ipairs(fruits) do
print(index .. ": " .. fruit)
end The generic
for loop works with any iterator function. ipairs returns an index-value iterator for array tables. pairs returns a key-value iterator for all keys. Custom iterators are just functions that return the next value each time they are called.break
[1, 2, 3, 4, 5].each do |number|
break if number == 3
puts number
end for number = 1, 5 do
if number == 3 then break end
print(number)
end Lua's
break exits the innermost loop, just like Ruby's. Lua has no next keyword for skipping iterations β the equivalent is wrapping the loop body in an if statement.goto (Lua 5.2+)
# Ruby has no goto; use next in loops
(1..5).each do |i|
next if i == 3
puts i
end for i = 1, 5 do
if i == 3 then goto continue end
print(i)
::continue::
end Lua 5.2 added
goto primarily to simulate continue (skip to next iteration), which Lua lacks. Labels are written ::name::. The goto is controversial but the continue pattern is widely used and accepted.Functions
Defining Functions
def greet(name)
"Hello, #{name}!"
end
puts greet("Alice") local function greet(name)
return "Hello, " .. name .. "!"
end
print(greet("Alice")) Functions in Lua always require an explicit
return β there is no implicit last-expression return like in Ruby. local function f() is syntactic sugar for local f = function(). Without local, the function becomes a global.Multiple Return Values
def min_max(numbers)
[numbers.min, numbers.max]
end
min, max = min_max([3, 1, 4, 1, 5, 9])
puts "#{min}, #{max}" local function min_max(numbers)
local minimum, maximum = numbers[1], numbers[1]
for _, value in ipairs(numbers) do
if value < minimum then minimum = value end
if value > maximum then maximum = value end
end
return minimum, maximum
end
local minimum, maximum = min_max({3, 1, 4, 1, 5, 9})
print(minimum, maximum) Lua functions can return multiple values without wrapping them in a table. The caller receives them as separate variables. This is a first-class language feature β not a tuple or array. In Ruby, multiple returns require returning an array and destructuring it.
Variadic Functions
def sum(*numbers)
numbers.sum
end
puts sum(1, 2, 3, 4, 5) local function sum(...)
local total = 0
for _, value in ipairs({...}) do
total = total + value
end
return total
end
print(sum(1, 2, 3, 4, 5)) Lua uses
... (three dots) for variadic arguments. Inside the function, ... is an expression that evaluates to the extra arguments β collect them into a table with {...}. select("#", ...) returns the count of extra arguments including trailing nils.Closures
def make_counter
count = 0
-> { count += 1; count }
end
counter = make_counter
puts counter.call
puts counter.call
puts counter.call local function make_counter()
local count = 0
return function()
count = count + 1
return count
end
end
local counter = make_counter()
print(counter()) -- 1
print(counter()) -- 2
print(counter()) -- 3 Lua closures capture upvalues (variables from the enclosing scope) just like Ruby lambdas. Each call to
make_counter creates an independent counter with its own count upvalue.First-Class Functions
double = ->(x) { x * 2 }
transform = method(:puts)
[1, 2, 3].map(&double).each(&transform) local double = function(x) return x * 2 end
local function apply(func, numbers)
local result = {}
for i, v in ipairs(numbers) do
result[i] = func(v)
end
return result
end
local doubled = apply(double, {1, 2, 3})
for _, v in ipairs(doubled) do print(v) end Functions are first-class values in Lua β they can be stored in variables, passed as arguments, and returned from other functions. This is identical in principle to Ruby's lambdas and procs, but Lua uses this pattern far more pervasively since there is no separate block syntax.
Default Arguments
def greet(name, greeting = "Hello")
"#{greeting}, #{name}!"
end
puts greet("Alice")
puts greet("Bob", "Hi") local function greet(name, greeting)
greeting = greeting or "Hello"
return greeting .. ", " .. name .. "!"
end
print(greet("Alice"))
print(greet("Bob", "Hi")) Lua has no default parameter syntax. The standard idiom is
param = param or default_value. This works because missing arguments receive nil, and the or operator returns the right side when the left is falsy. The caveat: it fails if false is a legitimate value for that argument.Named Arguments via Table
def create_user(name:, age:, admin: false)
"#{name}, #{age}, admin=#{admin}"
end
puts create_user(name: "Alice", age: 30) local function create_user(options)
local name = options.name
local age = options.age
local admin = options.admin or false
return name .. ", " .. age .. ", admin=" .. tostring(admin)
end
print(create_user({ name = "Alice", age = 30 })) Lua has no keyword arguments. The idiomatic substitute is passing a single table as the argument. When calling a function with a single table literal, the parentheses can be omitted:
create_user{ name="Alice", age=30 }.Metatables & OOP
__index β Property Lookup
class Person
attr_reader :name, :age
def initialize(name, age)
@name = name
@age = age
end
end
person = Person.new("Alice", 30)
puts person.name local Person = {}
Person.__index = Person
function Person.new(name, age)
return setmetatable({ name = name, age = age }, Person)
end
local person = Person.new("Alice", 30)
print(person.name) -- "Alice" The
__index metamethod controls what happens when a key is not found in a table. Setting __index to a table makes Lua look up missing keys there β the basis for inheritance in Lua. setmetatable(obj, mt) attaches a metatable to an object.Colon Syntax for Methods
class Person
def initialize(name) = @name = name
def greet = "Hello, I am #{@name}"
end
puts Person.new("Alice").greet local Person = {}
Person.__index = Person
function Person.new(name)
return setmetatable({ name = name }, Person)
end
function Person:greet() -- colon passes self implicitly
return "Hello, I am " .. self.name
end
local person = Person.new("Alice")
print(person:greet()) The colon syntax for method definition and calls (
obj:method()) automatically passes the object as the first argument named self. It is syntactic sugar for obj.method(obj). This is how Lua simulates instance methods.__tostring β String Representation
class Point
def initialize(x, y)
@x = x; @y = y
end
def to_s = "(#{@x}, #{@y})"
end
puts Point.new(3, 4) local Point = {}
Point.__index = Point
function Point.new(x, y)
return setmetatable({ x = x, y = y }, Point)
end
Point.__tostring = function(point)
return "(" .. point.x .. ", " .. point.y .. ")"
end
local point = Point.new(3, 4)
print(tostring(point)) -- "(3, 4)" The
__tostring metamethod is called by tostring(). Note that print() calls tostring() on each argument, so this also controls how objects appear in print output.Operator Overloading
class Vector
attr_reader :x, :y
def initialize(x, y)
@x = x; @y = y
end
def +(other) = Vector.new(@x + other.x, @y + other.y)
def to_s = "(#{@x}, #{@y})"
end
puts (Vector.new(1, 2) + Vector.new(3, 4)) local Vector = {}
Vector.__index = Vector
function Vector.new(x, y)
return setmetatable({ x = x, y = y }, Vector)
end
Vector.__add = function(a, b)
return Vector.new(a.x + b.x, a.y + b.y)
end
Vector.__tostring = function(v)
return "(" .. v.x .. ", " .. v.y .. ")"
end
local result = Vector.new(1, 2) + Vector.new(3, 4)
print(tostring(result)) Arithmetic operators map to metamethods:
__add for +, __sub for -, __mul for *, __div for /, __eq for ==, __lt for <, __concat for ...Inheritance
class Animal
def initialize(name) = @name = name
def speak = "..."
end
class Dog < Animal
def speak = "Woof! I am #{@name}"
end
puts Dog.new("Rex").speak local Animal = {}
Animal.__index = Animal
function Animal.new(name)
return setmetatable({ name = name }, Animal)
end
function Animal:speak() return "..." end
local Dog = setmetatable({}, { __index = Animal })
Dog.__index = Dog
function Dog.new(name)
return setmetatable(Animal.new(name), Dog)
end
function Dog:speak()
return "Woof! I am " .. self.name
end
print(Dog.new("Rex"):speak()) Inheritance chains metatable
__index lookups: when a key is not in Dog, Lua looks in Animal via the __index chain. This is prototype-based inheritance β similar to JavaScript β rather than class-based inheritance.Error Handling
Protected Calls with pcall
begin
raise "something went wrong"
rescue => error
puts "Caught: #{error.message}"
end local ok, err = pcall(function()
error("something went wrong")
end)
if not ok then
print("Caught: " .. err)
end pcall (protected call) calls a function and catches any errors. It returns true plus the function's return values on success, or false plus an error message on failure. This is Lua's equivalent of Ruby's begin/rescue/end.Raising Errors
def divide(a, b)
raise ArgumentError, "division by zero" if b == 0
a / b
end
p divide(10, 2) local function divide(a, b)
if b == 0 then
error("division by zero", 2) -- level 2 = caller's location
end
return a / b
end
local ok, result = pcall(divide, 10, 2)
if ok then print(result) end error(message, level) raises an error. The second argument controls where the error is attributed in the stack trace: 1 = the error() call, 2 = the caller (usually the right choice), 0 = no location. Any Lua value can be an error β not just strings.xpcall β Error with Traceback
begin
raise "oops"
rescue => error
puts error.message
puts error.backtrace.first(3).join("\n")
end local function error_handler(err)
return err .. "\n" .. debug.traceback("", 2)
end
local ok, message = xpcall(function()
error("oops")
end, error_handler)
if not ok then
print(message)
end xpcall is like pcall but takes a message handler function that receives the error before the stack unwinds β allowing you to capture a full stack traceback. debug.traceback() generates the traceback string.assert
value = 10
raise "value must be positive" unless value > 0
puts value
begin
raise "value must be positive" unless -5 > 0
rescue RuntimeError => error
puts error.message
end local value = 10
assert(value > 0, "value must be positive")
print(value)
local ok, err = pcall(function()
assert(-5 > 0, "value must be positive")
end)
print(err) assert(condition, message) raises an error if the condition is falsy. It returns all its arguments on success β so local file = assert(io.open(path)) both checks for errors and passes the file handle through in one line.Error Objects
class AppError < StandardError
attr_reader :code
def initialize(message, code)
super(message)
@code = code
end
end
begin
raise AppError.new("not found", 404)
rescue AppError => error
puts "#{error.code}: #{error.message}"
end local function throw(message, code)
error({ message = message, code = code })
end
local ok, err = pcall(function()
throw("not found", 404)
end)
if not ok and type(err) == "table" then
print(err.code .. ": " .. err.message)
end Any Lua value can be thrown as an error β including tables. This lets you attach structured data to errors. Check
type(err) to distinguish your structured errors from plain string errors generated by Lua itself.Coroutines
Creating and Resuming
fiber = Fiber.new do
puts "step 1"
Fiber.yield
puts "step 2"
Fiber.yield
puts "step 3"
end
fiber.resume
fiber.resume
fiber.resume local routine = coroutine.create(function()
print("step 1")
coroutine.yield()
print("step 2")
coroutine.yield()
print("step 3")
end)
coroutine.resume(routine)
coroutine.resume(routine)
coroutine.resume(routine) Lua coroutines are first-class values, very similar to Ruby's
Fiber. coroutine.create returns a coroutine in suspended state. coroutine.resume runs it until the next yield or completion.Passing Values Through yield
producer = Fiber.new do
[1, 2, 3].each { |value| Fiber.yield value }
end
3.times { puts producer.resume } local function producer(items)
return coroutine.wrap(function()
for _, value in ipairs(items) do
coroutine.yield(value)
end
end)
end
for value in producer({1, 2, 3}) do
print(value)
end coroutine.wrap creates a coroutine and returns a function that resumes it each time it is called, returning the yielded values. The wrapped form works perfectly as a generic for iterator. Values passed to yield become the return values of resume.Coroutine Status
fiber = Fiber.new { Fiber.yield; "done" }
puts fiber.alive? # true before first resume local routine = coroutine.create(function()
coroutine.yield()
end)
print(coroutine.status(routine)) -- "suspended"
coroutine.resume(routine)
print(coroutine.status(routine)) -- "suspended" (at yield)
coroutine.resume(routine)
print(coroutine.status(routine)) -- "dead" coroutine.status returns "suspended", "running", "normal", or "dead". Resuming a dead coroutine returns false plus an error message. Lua coroutines are asymmetric β a coroutine can only yield to the thread that resumed it.Producer-Consumer Pattern
producer = Enumerator.new do |yielder|
[1, 4, 9, 16, 25].each { |v| yielder.yield v }
end
producer.each { |value| puts value if value > 5 } local function squares(limit)
return coroutine.wrap(function()
for i = 1, limit do
coroutine.yield(i * i)
end
end)
end
for value in squares(5) do
if value > 5 then print(value) end
end The coroutine-as-iterator pattern is the Lua equivalent of Ruby's
Enumerator. The generator function yields values lazily; the for loop pulls them one at a time. No explicit state management is needed β the coroutine's stack is the state.Standard Library
Math Library
puts Math.sqrt(16)
puts 3.7.floor
puts 3.2.ceil
puts [1, 2, 3, 4, 5].max print(math.sqrt(16)) -- 4.0
print(math.floor(3.7)) -- 3
print(math.ceil(3.2)) -- 4
print(math.max(1,2,3,4,5)) -- 5
print(math.pi) -- 3.1415...
print(math.huge) -- inf The
math library covers the standard mathematical operations. math.huge is positive infinity. math.maxinteger is the largest integer. math.random() and math.randomseed() provide pseudo-random numbers.Iterating Pattern Matches
"one two three".scan(/w+/).each { |word| puts word } for word in string.gmatch("one two three", "%a+") do
print(word)
end string.gmatch returns an iterator over all matches of a pattern. Lua patterns use %a (letter), %d (digit), %s (space), %w (alphanumeric) where regex uses \w, \d, \s.Type Conversion
puts Integer("42")
puts Float("3.14")
puts 42.to_s
puts Integer("abc") rescue puts "conversion failed" print(tonumber("42")) -- 42
print(tonumber("3.14")) -- 3.14
print(tonumber("0xff")) -- 255
print(tonumber("abc")) -- nil (no error)
print(tostring(42)) -- "42"
print(tostring(true)) -- "true" tonumber returns nil on failure instead of raising an error β check the result before using it. An optional second argument specifies the base: tonumber("ff", 16) returns 255.Output Without Newline
print "no newline"
print " here"
puts io.write("no newline")
io.write(" here")
io.write("\n")
print("done") io.write outputs a string without appending a newline. print always appends a newline and converts each argument with tostring. For formatted output, use io.write(string.format(...)).Unpacking a Table
args = [10, 20, 30]
puts args.sum
puts [*args, 40] local args = {10, 20, 30}
print(table.unpack(args)) -- 10 20 30
local function add(a, b, c)
return a + b + c
end
print(add(table.unpack(args))) -- 60 table.unpack expands a table into multiple values β the inverse of collecting ... into a table. This is how you pass a table's contents as separate arguments to a function, equivalent to Ruby's splat operator (func(*args)).Modules with require
# require loads a file once and caches it
require "json"
puts JSON.generate({ status: "ok", count: 3 })
# require_relative "./my_module" # loads relative to current file -- require loads a file once and caches the result
-- local json = require("dkjson") -- would need the module installed
-- Simulate a module inline (normally in its own file)
local mymodule = (function()
local module = {}
function module.greet(name)
return "Hello, " .. name
end
return module
end)()
print(mymodule.greet("Alice")) require("name") searches package.path for a file named name.lua, loads it, and caches the result in package.loaded. Subsequent calls to require with the same name return the cached value β equivalent to Ruby's require caching behavior.