-
-
Notifications
You must be signed in to change notification settings - Fork 197
Open
Description
UserData derive macro feature proposal
First, this is not a request for a feature. I am willing to implement the macro. This proposal is a brief overview of my idea. I will write the detailed proposal and start implementing the macro only after the maintainer confirms this idea makes sense and may be accepted into mlua.
Overview
I suggest implementing:
- Derive macro for the
UserDatatrait, where user may specify fields and mutable fields with attributes - Attribute macro placed on
implblock where user may specify with attributes:- get/set field methods
- normal methods
- metamethods
- Methods may return
mlua::Resultor return a value if marked withinfallible - Methods may optionally accept
&Luaand other arguments
Example
#[derive(Clone, UserData)]
struct MyStruct {
#[field]
a: u64,
#[field_mut]
b: u64,
}
#[mlua::userdata_impl]
impl MyStruct {
#[field_get(name = "s", infallible)]
fn sum(&self) -> u64 {
self.a + self.b
}
#[method(infallible)]
fn double_a(&mut self) {
self.a *= 2;
}
#[method]
fn parse_and_set_b(&mut self, _: &mlua::Lua, b: String) -> mlua::Result<()> {
self.b = b.parse().map_err(|e| mlua::Error::ExternalError(Arc::new(e)))?;
Ok(())
}
#[meta_add(infallible)]
fn add(&self, _: &mlua::Lua, other: Self) -> Self {
Self {
a: self.a + other.a,
b: self.b + other.b,
}
}
}
#[test]
fn test_userdata_derive() {
let lua = Lua::new();
lua.globals().set("A", MyStruct { a: 1, b: 0 }).unwrap();
lua.globals().set("B", MyStruct { a: 2, b: 4 }).unwrap();
let chunk = lua.load(
r#"
A:double_a()
B:parse_and_set_b "15"
A.b = A.a
C = A + B
return C.s
"#,
);
assert_eq!(chunk.eval::<f64>().unwrap(), 21.0);
}The expected macro expansion is something like this (of course with proper hygiene and static assertions, this was written manually):
#[derive(Clone, FromLua)]
struct MyStruct {
a: u64,
b: u64,
}
impl MyStruct {
fn sum(&self) -> u64 {
self.a + self.b
}
fn double_a(&mut self) {
self.a *= 2;
}
fn parse_and_set_b(&mut self, _: &mlua::Lua, b: String) -> mlua::Result<()> {
self.b = b.parse().map_err(|e| mlua::Error::ExternalError(Arc::new(e)))?;
Ok(())
}
fn add(&self, _: &mlua::Lua, other: Self) -> Self {
Self {
a: self.a + other.a,
b: self.b + other.b,
}
}
}
impl UserData for MyStruct
where
MyStruct: UserDataImpl,
{
fn add_fields<F: LuaUserDataFields<Self>>(fields: &mut F) {
<MyStruct as UserDataImpl>::add_fields(fields);
fields.add_field_method_get("a", |_, this| Ok(this.a));
fields.add_field_method_get("b", |_, this| Ok(this.b));
fields.add_field_method_set("b", |_, this, b: u64| {
this.b = b;
Ok(())
});
}
fn add_methods<M: LuaUserDataMethods<Self>>(methods: &mut M) {
<MyStruct as UserDataImpl>::add_methods(methods);
}
}
impl UserDataImpl for MyStruct {
fn add_fields<F: LuaUserDataFields<Self>>(fields: &mut F) {
fields.add_field_method_get("s", |_, this| Ok(this.sum()));
}
fn add_methods<M: LuaUserDataMethods<Self>>(methods: &mut M) {
methods.add_method_mut("double_a", |_, this, _: ()| {
this.double_a();
Ok(())
});
methods.add_method_mut("parse_and_set_b", |lua, this, (b,): (String,)| {
this.parse_and_set_b(lua, b)
});
methods.add_meta_method(MetaMethod::Add, |lua, this, (other,): (Self,)| {
Ok(this.add(lua, other))
});
}
}where
pub trait UserDataImpl: Sized {
fn add_fields<F: LuaUserDataFields<Self>>(fields: &mut F);
fn add_methods<M: LuaUserDataMethods<Self>>(methods: &mut M);
}The macro expansion was written manually. I apologize if there are mismatches between the expanded and the original code.
Edit: Added missing field getters/setters, fixed a typo
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
No labels