Records
Records are the third major type of table supported in Teal. They represent another super common pattern in Lua code, so much that Lua includes special syntax for it (the dot and colon notations for indexing): tables with a set of string keys known in advance, each of them corresponding to a possibly different value type. Records (named as such in honor of the Algol/Pascal tradition from which Lua gets much of the feel of its syntax) can be used to represent objects, "structs", etc.
To declare a record variable, you need to create a record type first.
The type describes the set of valid fields (keys of type string and their values
of specific types) this record can take. You can declare types using local type
and global types using global type
.
local type Point = record
x: number
y: number
end
Types are constant: you cannot reassign them, and they must be initialized with a type on declaration.
Just like with functions in Lua, which can be declared either with local f = function()
or with local function f()
, there is also a shorthand syntax
available for the declaration of record types:
local record Point
x: number
y: number
end
Tables that match the shape of the record type will be accepted as an initializer of variables declared with the record type:
local p: Point = { x = 100, y = 100 }
This, however, won't work:
local record Vector
x: number
y: number
end
local v1: Vector = { x = 100, y = 100 }
local p2: Point = v1 -- Error!
Just because a table has fields with the same names and types, it doesn't mean that it is a Point. This is because records in Teal are nominal types.
You can always force a type, though, using the as
operator:
local p2 = v1 as Point -- Teal goes "ok, I'll trust you..."
Note we didn't even have to declare the type of p2. The as
expression resolves
as a Point, so p2 picks up that type.
You can also declare record functions after the record definition using the regular Lua colon or dot syntax, as long as you do it in the same scope block where the record type is defined:
function Point.new(x: number, y: number): Point
local self: Point = setmetatable({}, { __index = Point })
self.x = x or 0
self.y = y or 0
return self
end
function Point:move(dx: number, dy: number)
self.x = self.x + dx
self.y = self.y + dy
end
When using the function, don't worry: if you get the colon or dot mixed up, tl will detect and tell you about it!
If you want to define the function in a later scope (for example, if it is a callback to be defined by users of a module you are creating), you can declare the type of the function field in the record and fill it later from anywhere:
local record Obj
location: Point
draw: function(Obj)
end
A record can also store array data, by declaring an array interface. You can use it both as a record, accessing its fields by name, and as an array, accessing its entries by number. A record can have only one array interface.
local record Node is {Node}
weight: number
name: string
end
Note the recursive definition in the above example: records of type Node can be organized as a tree using its array part.
Finally, records can contain nested record type definitions. This is useful when exporting a module as a record, so that the types created in the module can be used by the client code which requires the module.
local record http
record Response
status_code: number
end
get: function(string): Response
end
return http
You can then refer to nested types with the normal dot notation, and use it across required modules as well:
local http = require("http")
local x: http.Response = http.get("http://example.com")
print(x.status_code)