Hello, Typed World
This is the shortest example that shows something Lua cannot do on its own. It is a single file, it
runs with nebra run, and every line of it has a reason to exist.
The program
--- Formats a person's display name.
---
--- Falls back to a placeholder when no name was supplied, so the caller never
--- has to deal with nil coming back out.
---@param name the raw name, possibly nil
---@return string a name that is always safe to print
function displayName(name: string?): string
local raw: string = name ?? ""
local trimmed: string, replacements: number = string.gsub(raw, "^%s*(.-)%s*$", "%1")
if #trimmed == 0 then
return "anonymous"
end
return trimmed
end
--- Greets everyone in a list, one line each.
function greetAll(names: string?[]): nil
for _, entry in ipairs(names) do
print("Hello, " .. displayName(entry) .. "!")
end
end
greetAll({ "Ada", " Grace ", "", "Alan" })
nebra run
Hello, Ada!
Hello, Grace!
Hello, anonymous!
Hello, Alan!
What is doing the work
name: string? says this parameter may be nil. That is not a comment, it is a contract. Calling
displayName(42) is a compile error, and so is doing #name before you have dealt with the nil.
?? is nil coalescing. name ?? "" produces the string when there is one and "" when there
is not, which is exactly the shape string.gsub wants.
local trimmed: string, replacements: number = string.gsub(...) destructures a multi-return.
string.gsub returns the new string and a replacement count, and Nebra knows that from its stdlib
declaration. Writing local trimmed: string = string.gsub(...) is an error, because the value on
the right is a (string, number) tuple and not a string. Lua would have silently discarded the
second value. Here you have to acknowledge it.
string?[] is an array whose elements may each be nil. Note where the ? sits: it binds to the
element type. string[]? would instead mean "an array that may itself be nil". The suffixes read
left to right.
: nil as a return type means the function returns nothing. You can also write : void, an
alias for the same thing that reads better when you mean "no result" rather than "the nil value".
The --- comments are doc comments in LuaCATS style. They show up on hover in the editor and
feed nebra docs. Ordinary -- comments do not.
What the compiler emits
function displayName(name)
local raw = (function() local __v = (name); if __v ~= nil then return __v else return ("") end end)()
local trimmed, replacements = string.gsub(raw, "^%s*(.-)%s*$", "%1")
if #trimmed == 0 then
return "anonymous"
end
return trimmed
end
function greetAll(names)
for _, entry in ipairs(names) do
print("Hello, " .. displayName(entry) .. "!")
end
end
greetAll({
"Ada",
" Grace ",
"",
"Alan"
})
Every annotation is gone. There is no type table, no runtime check and nothing to require. This is the property worth internalising early: the type system is a compile-time argument between you and the compiler, and the runtime never hears about it.
The one construct that expands is ??, which becomes a small immediately-invoked function. That is
deliberate: it evaluates the left side exactly once, which the usual a and b or c idiom cannot
guarantee when the left side is false.
This output was produced with [reflection] mode = "none" in nebra.toml. Reflection metadata is
emitted by default, which adds a registry block at the top of the file. See
Reflection for what it is and when you want it.
A method-call gotcha
Lua lets you write (" x "):gsub(...) because strings carry a metatable. Nebra does not model that
out of the box, so this is an error:
local upper: string = string.upper(s) -- correct
local upper: string = s:upper() -- error: type 'string' has no method 'upper'
Call the library function directly, or add the method yourself with an
extend string block if you want the receiver syntax across your codebase.
Turning the safety up
Add this to nebra.toml:
[rules]
strict_nil = true
Now the compiler refuses to let a possibly-nil value be used where a non-nil one is required. Drop
the ?? "" and you get:
error[E2006]: Expression of type 'string | nil' is possibly nil. Use '?.' to access fields safely or check for nil first.
Strict-nil mode is off by default so that existing Lua compiles unchanged. Turning it on is the
single highest-value flag in nebra.toml for a codebase you intend to maintain.
Next
- Classes and interfaces puts structure around this.
- Nilability covers
?.,!, flow narrowing and thenevertype. - Type system is the full reference for what you can write after the colon.