The @class annotation allows you to define classes or interfaces in Lua, supporting inheritance, field definitions, and access control modifiers.
Syntax
-- Basic class definition
---@class <class_name>[: <parent_class1>[, <parent_class2>...]]
-- Exact class definition (prohibits dynamic field addition)
---@class (exact) <class_name>[: <parent_class>...]
-- Partial class definition (extends existing classes)
---@class (partial) <class_name>
Basic Class Definition
Define a simple class with fields:
---@class Animal
---@field name string Animal name
---@field species string Species
---@field age number Age
local Animal = {}
---@param name string
---@param species string
---@param age number
function Animal.new(name, species, age)
return setmetatable({
name = name,
species = species,
age = age
}, {__index = Animal})
end
function Animal:speak()
print(self.name .. " makes a sound")
end
Line-by-line explanation:
- Line 1: Define the
Animal class
- Lines 2-4: Define fields with their types and descriptions
- Line 5: Create the class table
- Lines 7-9: Document the constructor parameters
- Lines 10-15: Constructor creates instances using metatables
- Lines 17-19: Instance method using
self
Use descriptive comments after field types to document what each field represents. This improves autocomplete suggestions.
Single Inheritance
Extend a class to create a subclass:
---@class Dog : Animal
---@field breed string Breed
---@field isVaccinated boolean Whether vaccinated
local Dog = setmetatable({}, {__index = Animal})
function Dog:speak()
print(self.name .. " barks: Woof!")
end
---@param name string
---@param breed string
---@param age number
---@return Dog
function Dog.new(name, breed, age)
local self = Animal.new(name, "Canine", age)
self.breed = breed
self.isVaccinated = false
return setmetatable(self, {__index = Dog})
end
Key points:
Dog : Animal declares Dog inherits from Animal
Dog instances have access to all Animal fields and methods
- Methods can be overridden (like
speak() in this example)
- The constructor calls the parent constructor and adds subclass-specific fields
Multiple Inheritance
Inherit from multiple parent classes:
---@class Flyable
---@field maxAltitude number Maximum flight altitude
---@class Swimmable
---@field maxDepth number Maximum diving depth
---@class Duck : Animal, Flyable, Swimmable
---@field featherColor string Feather color
local Duck = {}
How it works:
Duck inherits fields from Animal, Flyable, and Swimmable
- All parent fields are available on
Duck instances
- Field completion will include fields from all parent classes
Multiple inheritance is supported for type checking and autocomplete, but you must implement the metatable logic yourself to achieve runtime behavior. Lua doesn’t automatically merge methods from multiple parents.
Exact Classes
Prevent dynamic field addition to ensure type safety:
---@class (exact) Point
---@field x number
---@field y number
local Point = {}
local p = {x = 10, y = 20}
p.z = 30 -- Warning: 'z' is not a valid field of Point
Use exact classes when:
- You want to prevent accidental typos in field names
- The class has a fixed structure that shouldn’t change
- You want stricter type checking
Exact classes help catch bugs where you accidentally create new fields instead of using existing ones.
Partial Classes
Extend an existing class definition from another file or module:
-- In another file, Animal was already defined
---@class (partial) Animal
---@field weight number Weight
When to use partial:
- Adding fields to a class defined elsewhere
- Extending third-party library types
- Splitting large class definitions across files
Generic Classes
Create reusable container types with type parameters:
---@class Container<T>
---@field private items T[] Stored items
---@field capacity number Capacity
local Container = {}
---@generic T
---@param capacity number
---@return Container<T>
function Container.new(capacity)
return {items = {}, capacity = capacity}
end
---@param item T
function Container:add(item)
if #self.items < self.capacity then
table.insert(self.items, item)
end
end
Usage:
---@type Container<string>
local stringContainer = Container.new(10)
stringContainer:add("Hello") -- OK
stringContainer:add(42) -- Warning: expected string, got number
Line-by-line explanation:
- Line 1: Define generic class with type parameter
T
- Line 2: Field type uses the generic parameter
T[]
- Line 6: Constructor is marked as generic
- Line 13: Method parameter uses the generic type
T
Field Visibility
Control field access with visibility modifiers:
---@class Account
---@field public username string Public field
---@field protected email string Protected field (subclasses only)
---@field private password string Private field (this class only)
local Account = {}
Visibility levels:
public: Accessible everywhere (default)
protected: Accessible in the class and its subclasses
private: Accessible only within the class
Visibility modifiers are enforced by the language server for type checking and warnings, but not at runtime. Lua doesn’t have true private fields.
Practical Example: User Management
---@class User
---@field id number User ID
---@field name string Full name
---@field email string Email address
---@field private passwordHash string Hashed password
local User = {}
---@param id number
---@param name string
---@param email string
---@return User
function User.new(id, name, email)
return setmetatable({
id = id,
name = name,
email = email,
passwordHash = ""
}, {__index = User})
end
---@param password string
function User:setPassword(password)
self.passwordHash = hashPassword(password)
end
---@param password string
---@return boolean
function User:verifyPassword(password)
return self.passwordHash == hashPassword(password)
end
---@class AdminUser : User
---@field accessLevel number Admin access level
---@field permissions string[] Admin permissions
local AdminUser = setmetatable({}, {__index = User})
---@param id number
---@param name string
---@param email string
---@param accessLevel number
---@return AdminUser
function AdminUser.new(id, name, email, accessLevel)
local self = User.new(id, name, email)
self.accessLevel = accessLevel
self.permissions = {"read", "write", "delete"}
return setmetatable(self, {__index = AdminUser})
end
Usage Example
---@type Dog
local myDog = Dog.new("Buddy", "Golden Retriever", 3)
myDog:speak() -- Output: Buddy barks: Woof!
---@type Container<string>
local stringContainer = Container.new(10)
stringContainer:add("Hello")
stringContainer:add("World")
Best Practices
- Document all fields: Include type and description for every field
- Use inheritance wisely: Only inherit when there’s a true “is-a” relationship
- Prefer composition over deep inheritance: Shallow hierarchies are easier to maintain
- Use exact classes for DTOs: Data transfer objects benefit from strict field checking
- Mark internal fields as private: Hide implementation details
- Use generics for containers: Avoid type-unsafe collections
Common Patterns
Builder Pattern
---@class ConfigBuilder
---@field private config table
local ConfigBuilder = {}
function ConfigBuilder.new()
return setmetatable({config = {}}, {__index = ConfigBuilder})
end
---@param host string
---@return ConfigBuilder
function ConfigBuilder:setHost(host)
self.config.host = host
return self -- Return self for chaining
end
---@return table
function ConfigBuilder:build()
return self.config
end
Singleton Pattern
---@class Logger
---@field private static instance Logger?
local Logger = {}
Logger.instance = nil
---@return Logger
function Logger.getInstance()
if not Logger.instance then
Logger.instance = setmetatable({}, {__index = Logger})
end
return Logger.instance
end
Combine @class with @generic, @param, and @return annotations for comprehensive type coverage.