Nilability & Optionals
Nebra tracks nullability in its type system and provides operators for safe nil handling.
Nullable Types
Append ? to a type to allow nil:
local name: string? = nil -- OK
local count: number = nil -- ERROR: number is not nullable
Strict Nil Mode
With strict_nil = true in config, the compiler requires explicit handling of nullable values before using them as non-nil:
local name: string? = getName()
print(name) -- WARNING: name might be nil
if name ~= nil then
print(name) -- OK: narrowed to string
end
Non-Nil Assertion (!)
Assert that a value is not nil. The type checker treats the result as non-nullable:
local name: string? = getName()
local safe: string = name! -- assert non-nil
print(name!) -- use inline
This is a compile-time assertion only. No runtime code is emitted. If the value is nil at runtime, Lua will produce its normal nil errors.
Optional Chaining (?.)
Access fields on a potentially nil object. Returns nil if the object is nil:
local city = user?.address?.city
Compiles to:
local city
do
local __t = user
if __t ~= nil then
__t = __t.address
if __t ~= nil then
city = __t.city
end
end
end
Optional Call (?())
Call a function only if it's not nil:
local result = callback?()
local value = obj?.method?()
Nil Coalescing (??)
Provide a default value when an expression is nil:
local name = user?.name ?? "Anonymous"
local port = config.port ?? 8080
local list = getData() ?? {}
Right-associative: a ?? b ?? c means a ?? (b ?? c).
Nil Narrowing in Conditionals
The type checker narrows types based on nil checks:
local x: string? = getInput()
if x ~= nil then
-- x is narrowed to `string` here
print(x .. "!")
end
if x == nil then
-- x is `nil` here
return
end
-- x is `string` after the nil guard
A guard branch counts as taken care of when it exits by any means - return, break, or a call to
a function that returns never:
local y: string? = getInput()
if y == nil then
error("no input")
end
print(#y) -- y is `string` here
local z: string = getInput() or error("no input")
The narrowing lasts until the variable is assigned again, which restores its declared type:
if y == nil then return end
-- y: string
y = nil -- allowed: the assignment ends the narrowing
Configuration
[rules]
strict_nil = false # true: require explicit nil handling