You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何实现FromLua trait将Lua中的UserData转回Rust结构体?

解决mlua中UserData类型转回Rust结构体的问题

你遇到的错误核心是:FromLua实现中误用了serde的反序列化逻辑——lua.from_value()期望输入是Lua表(table),但实际传入的是UserData类型,导致类型不匹配报错。

解决方案:直接从UserData下转为Rust结构体

mlua的UserData本质是将Rust类型实例包装后存储在Lua中,无需通过serde反序列化,直接通过UserData::downcast方法即可提取内部的Rust实例。

修改FromLua的实现如下:

impl<'lua> FromLua<'lua> for Cars {
    fn from_lua(value: Value<'lua>, _lua: &'lua Lua) -> Result<Self> {
        // 判断是否为UserData类型,尝试下转为Cars实例
        if let Value::UserData(ud) = value {
            ud.borrow::<Cars>().cloned()
        } else {
            Err(mlua::Error::FromLuaConversionError {
                from: value.type_name(),
                to: "Cars",
                message: Some("expected userdata of type Cars".into()),
            })
        }
    }
}

修正后的完整代码

use serde::{Deserialize, Serialize};
use mlua::{FromLua, Lua, Value, Result, UserData, UserDataMethods};

#[derive(Default, Debug, Serialize, Deserialize, Clone )]
struct Car {
    brand: String,
    model: String,
}

impl UserData for Car { }

#[derive(Default, Debug, Serialize, Deserialize, Clone )]
struct Cars {
    list: Vec<Car>
}

impl Cars {
    fn new() -> Self {
        Self { list: Vec::new() }
    }

    fn add_car(&mut self, brand: String, model: String) {
        self.list.push( Car { brand, model } );
    }
}

impl UserData for Cars {
    fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) {
        methods.add_method_mut("add", |_, this, (brand, model): (String, String)| {
            println!("added car lua [brand: {}, name: {}]", &brand, &model);
            this.list.push( Car { brand, model });
            println!("{:#?}", this.list);
            Ok(())
        });
    }
}

impl<'lua> FromLua<'lua> for Cars {
    fn from_lua(value: Value<'lua>, _lua: &'lua Lua) -> Result<Self> {
        if let Value::UserData(ud) = value {
            ud.borrow::<Cars>().cloned()
        } else {
            Err(mlua::Error::FromLuaConversionError {
                from: value.type_name(),
                to: "Cars",
                message: Some("expected userdata of type Cars".into()),
            })
        }
    }
}

fn main() -> Result<()> {
    let lua = Lua::new();
    let mut cars = Cars::new();
    cars.add_car("Toyota".to_string(), "Corolla".to_string());

    println!("{:#?}", &cars);

    lua.globals().set("Cars", cars)?;

    lua.load(r#"
        local cars = Cars
        cars:add("Toyota", "Lua")
        cars:add("Honda", "Lua")

        print( type(cars) ) -- userdata 
        print( typeof(cars) ) -- Cars
    "#).exec()?;

    let final_cars: Cars = lua.globals().get("Cars")?;

    println!("Final car\n{:#?}", final_cars);
    Ok(())
}

关键说明

  • 当你将实现了UserData的Rust类型传入Lua时,mlua会自动将其包装为userdata,无需额外序列化操作
  • 转回Rust时,直接通过UserData::borrow获取内部实例的引用,再借助Clone trait拿到所有权(如果你的类型没有实现Clone,可以考虑使用take方法转移所有权,具体需根据业务场景调整)

内容的提问来源于stack exchange,提问作者SymmetricsWeb

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.01 08:13:26