The #[lua_methods] attribute macro is placed on an impl block to automatically wrap pub fn items as Lua-callable functions.
It handles two kinds of functions:
| Function Type | Signature | Lua Call Syntax | Generated Into |
|---|---|---|---|
| Instance method | &self / &mut self |
obj:method(args) |
__lua_lookup_method() |
| Associated function | No self (e.g. constructor) |
Type.func(args) |
__lua_static_methods() |
Methods with &self or &mut self become instance methods:
#[lua_methods]
impl Point {
/// &self — read-only method
pub fn distance(&self) -> f64 {
(self.x * self.x + self.y * self.y).sqrt()
}
/// &mut self — mutating method
pub fn translate(&mut self, dx: f64, dy: f64) {
self.x += dx;
self.y += dy;
}
}Called in Lua with : syntax:
local d = p:distance() -- calls &self method
p:translate(10, 20) -- calls &mut self methodYou can also use . syntax to get a method reference (CFunction):
local f = p.distance -- type(f) == "function"
print(f(p)) -- equivalent to p:distance()pub fn items without a self parameter become associated functions, typically used as constructors:
#[lua_methods]
impl Point {
/// Constructor — no self parameter, returns Self
pub fn new(x: f64, y: f64) -> Self {
Point { x, y }
}
/// Another constructor
pub fn origin() -> Self {
Point { x: 0.0, y: 0.0 }
}
}After registration via register_type, call them in Lua with . syntax:
local p = Point.new(3, 4) -- calls the associated function
local o = Point.origin() -- another constructorWhen an associated function returns Self, the macro automatically:
- Calls the Rust function to get a struct instance
- Wraps it in
LuaUserdata::new(result) - Allocates it via
create_userdata(GC-managed) - Pushes it onto the Lua stack as a userdata return value
The Lua side receives a full userdata object with field access, method calls, and metamethods.
| Condition | Wrapper Generated? |
|---|---|
pub fn method(&self, ...) |
✅ Instance method |
pub fn method(&mut self, ...) |
✅ Mutable instance method |
pub fn func(args...) -> ... |
✅ Associated function |
fn private_method(&self, ...) |
❌ Skipped (not pub) |
pub async fn ... |
❌ Skipped (async not supported) |
Only pub functions are processed. Private methods are never exposed to Lua.
Use #[lua(skip)] to exclude a pub method from Lua exposure:
#[lua_methods]
impl Vec2 {
pub fn length(&self) -> f64 {
(self.x * self.x + self.y * self.y).sqrt()
}
/// Internal helper — pub for Rust callers, but hidden from Lua
#[lua(skip)]
pub fn raw_components(&self) -> (f64, f64) {
(self.x, self.y)
}
}In Lua, raw_components will not be found:
local v = Vec2.new(3, 4)
print(v:length()) -- 5.0
print(v:raw_components()) -- error: method not foundThis is useful when a method must be pub for Rust but should not be part of the Lua API.
#[lua_methods]
impl MyType {
pub fn example(
&self,
a: i64, // integer
b: f64, // float
c: bool, // boolean
d: String, // string
e: Option<String>, // optional (nil → None)
) -> f64 {
// ...
}
}See Type Conversions for the full conversion reference.
#[lua_methods]
impl MyType {
// No return value
pub fn action(&mut self) { /* ... */ }
// Basic type
pub fn get_value(&self) -> f64 { 42.0 }
// String
pub fn get_name(&self) -> String { "hello".into() }
// Option<T> — None becomes nil
pub fn find(&self, key: String) -> Option<i64> { None }
// Result<T, E> — Err triggers a Lua error
pub fn divide(&self, d: f64) -> Result<f64, String> {
if d == 0.0 { Err("div by zero".into()) }
else { Ok(self.value / d) }
}
// Self — wrapped as userdata (associated functions only)
pub fn new(x: f64) -> Self { MyType { value: x } }
}#[lua_methods] should only be used on one impl block. If multiple impl blocks use this attribute, the later one's __lua_lookup_method and __lua_static_methods will shadow the former (since they are same-name inherent methods).
// ✅ Recommended: put all methods in a single #[lua_methods] block
#[lua_methods]
impl Point {
pub fn new(x: f64, y: f64) -> Self { /* ... */ }
pub fn distance(&self) -> f64 { /* ... */ }
pub fn translate(&mut self, dx: f64, dy: f64) { /* ... */ }
}For the Point example above, #[lua_methods] roughly generates:
// Original impl block is preserved
impl Point {
pub fn new(x: f64, y: f64) -> Self { /* ... */ }
pub fn distance(&self) -> f64 { /* ... */ }
pub fn translate(&mut self, dx: f64, dy: f64) { /* ... */ }
}
// Additional generated impl block
impl Point {
/// Instance method lookup
pub fn __lua_lookup_method(key: &str) -> Option<CFunction> {
// Internally defines __lua_method_distance, __lua_method_translate, etc.
match key {
"distance" => Some(__lua_method_distance),
"translate" => Some(__lua_method_translate),
_ => None,
}
}
/// Associated function (static method) list
pub fn __lua_static_methods() -> &'static [(&'static str, CFunction)] {
// Internally defines __lua_static_new wrapper
&[("new", __lua_static_new)]
}
}Runnable example: See
Vec2inexamples/luars-example/src/main.rs—example_vec2()
- register_type — how to register associated functions in the Lua global table
- Type Conversions — detailed conversion rules for parameters and return values