Compare commits

..

56 Commits

Author SHA1 Message Date
Alex Orlenko 5f0e06fb66 Update CHANGELOG 2026-04-22 00:06:11 +01:00
Alex Orlenko 181c9d07b7 v0.12.0-rc.1 2026-04-20 00:24:58 +01:00
Alex Orlenko 4e827179d1 Update compile tests messages 2026-04-20 00:19:56 +01:00
Alex Orlenko cc26dcd4ff Bump rustyline 2026-04-20 00:16:34 +01:00
Alex Orlenko 201e30bc07 Add UserDataOwned<T> wrapper to take ownership of userdata T
It implements `FromLua` and takes ownership of a Lua userdata value.
The semantics is similar to `AnyUserData::take`, preventing any
further use from Lua.

Closes #686
2026-04-20 00:05:57 +01:00
Alex Orlenko 4e028d8409 Change AnyUserData::type_name to return LuaString instead.
This avoids unnecessary allocation and returns type name as it stored in metatable.
2026-04-19 23:26:24 +01:00
Alex Orlenko 3d1ae981d3 Update docs 2026-04-19 22:12:17 +01:00
Alex Orlenko 8c93948f2f Fix tests 2026-04-18 16:13:23 +01:00
Alex Orlenko f2b5cc44de traits module no longer need to be public
The LuaNativeFn traits were moved to the `function` module and all other traits
as re-exported.
2026-04-18 16:09:17 +01:00
Alex Orlenko 27f91dfd1b Accept any error in Function::wrap/wrap_mut/wrap_async
Previously wrapped functions were required to return `mlua::Result`.
Now it's possible to wrap functions returning any errors as long as
they implement `std::error::Error`.

Existing code remains compatible with `mlua::Result` as this type
is not converted to an external error.
2026-04-18 16:01:35 +01:00
Alex Orlenko 75ff11f795 Move LuaNativeFn/Mut into function module 2026-04-18 14:56:24 +01:00
Alex Orlenko df6097ab38 Mark Luau CompileConstant as non_exhaustive 2026-04-18 14:51:54 +01:00
Alex Orlenko 65bb6279ee Update serde types visibility 2026-04-18 14:47:26 +01:00
Alex Orlenko 31b88e85bb cargo fmt 2026-04-18 13:26:42 +01:00
Alex Orlenko 3be4745190 Add initial Luau integer64 type support
RFC: https://rfcs.luau.org/type-long-integer.html
Unfortunately this type is not backward compatible with regular numbers
and require a special "integer" library.
It's not integrated with `Value` enum to keep it simple.
2026-04-18 13:14:05 +01:00
Alex Orlenko c52deec988 Use c_int for userdata metatable id 2026-04-18 13:14:05 +01:00
三咲雅 misaki masa 3ab3c997b3 feat: support external strings for Cow<str> and Cow<CStr> (#692) 2026-04-09 10:45:15 +01:00
Alex Orlenko 5872ed70f5 Make traits module public 2026-04-04 15:04:00 +01:00
Alex Orlenko e7e92b4f6f Make chunk module public 2026-04-03 13:58:05 +01:00
Alex Orlenko d27693b61a Make error module public 2026-03-29 10:26:45 +01:00
Alex Orlenko c9848d6faf Update doc/example for (hidden) Lua::exec_raw_lua 2026-03-28 23:57:35 +00:00
Alex Orlenko 9126bb8ce0 Add RawLua::pop method 2026-03-28 22:37:25 +00:00
Alex Orlenko 7f1d716a44 Remove deprecated Lua::load_from_function 2026-03-28 17:40:22 +00:00
Alex Orlenko be56e2205c Update GC step_size doc 2026-03-28 17:38:49 +00:00
Alex Orlenko c5aadc68cd Open UserDataMethods::add_method_once and UserDataMethods::add_async_method_once 2026-03-28 17:17:57 +00:00
Alex Orlenko a5ae2a1fc3 Update useratom doc 2026-03-28 17:07:03 +00:00
Alex Orlenko 59872da63d mlua-sys: Bump lua-src and luajit-src dependencies 2026-03-28 16:31:19 +00:00
Alex Orlenko a24d2151af Minor fixes in docs 2026-03-27 22:54:56 +00:00
Alex Orlenko 56c227fd7e Make state module public 2026-03-08 00:06:18 +00:00
Alex Orlenko d5d66abe42 Refactor GC control API
- Replace `gc_inc/gc_gen` with `gc_set_mode`
- Add `GcIncParams` and `GcGenParams` for GC tuning
- Remove `gc_step_kbytes` (it's very rare needed and Lua 5.5 has changed the input param from kbytes to bytes)
2026-03-07 23:52:10 +00:00
Alex Orlenko a2d8b21964 Make luau module public 2026-03-01 16:40:19 +00:00
Alex Orlenko a9604c4946 Rename Luau's TextRequirer to FsRequirer 2026-03-01 16:27:41 +00:00
Alex Orlenko 81ae8e1393 Make Chunk::wrap public 2026-03-01 16:17:11 +00:00
Alex Orlenko a959b98d30 Add chunk module doc and update prelude re-exports 2026-03-01 16:11:38 +00:00
Alex Orlenko efd0856033 Derive PartialEq for Thread 2026-02-28 14:52:59 +00:00
Alex Orlenko c91066006f Make thread module public 2026-02-28 14:44:50 +00:00
Alex Orlenko a45fe9bb93 Bump luau-src to 0.19 (Luau 0.710) 2026-02-28 12:03:37 +00:00
Alex Orlenko f1a97e4193 Open Thread::state() that returns *mut lua_State pointer. 2026-02-28 11:48:59 +00:00
Alex Orlenko 47e6a37323 Add shortcuts to check thread status (Thread::is_resumable(), Thread::is_finished() etc) 2026-02-28 11:46:05 +00:00
Alex Orlenko bf0c96908f Remove lifetime from BorrowedStr and BorrowedBytes
The underlying `ValueRef` is cheap to clone as only increases reference count,
instead of allocating a new Lua stack slot.
2026-02-23 10:24:31 +00:00
Alex Orlenko 8817720362 Re-export (hidden) TablePairs and TableSequence 2026-02-23 09:51:24 +00:00
Alex Orlenko 35294359ad Inline doc for some types 2026-02-22 19:58:28 +00:00
Alex Orlenko eb76db59da Make userdata module public 2026-02-22 19:52:57 +00:00
Alex Orlenko 33bf3ffde7 Fix doc warnings 2026-02-22 14:37:23 +00:00
Alex Orlenko 0f3fdb0539 Make string module public 2026-02-22 14:33:38 +00:00
Alex Orlenko 79d438aaad Build CI docs on main branch 2026-02-22 13:28:48 +00:00
Alex Orlenko 30cf4bef58 Use RwLock directly instead of UserDataCell 2026-02-22 00:13:19 +00:00
Alex Orlenko 5776c72208 Use parking_lot::RwLock in UserDataCell container in "send" mode.
In non-send mode, mimic the `RwLock` API (using `Cell<isize>` counter).
We're continue manually operating the underlying `RawRwLock` for flexibility.
2026-02-21 23:18:39 +00:00
Alex Orlenko 943c3aed58 Some minor fixes in userdata cell 2026-02-21 21:19:42 +00:00
Alex Orlenko 8fcb6a8416 Update dependencies 2026-02-21 15:38:00 +00:00
Alex Orlenko 452dc8be88 clippy 2026-02-21 15:31:48 +00:00
Alex Orlenko 63a255bbc9 Replace is_sync specialization trick with MaybeSync trait bound.
The `is_sync::<T>()` runtime check relied on implicit specialization via
`Copy`/`Clone` array behavior, which has changed in Rust 1.86+.
`UserDataRef` always taking an exclusive lock even for `Sync` userdata,
preventing concurrent shared borrows.

With the `send` feature flag enabled, userdata types must now be `Send + Sync`.
This is a breaking change, `T: Send + !Sync` userdata types can be wrapped in a `Mutex`
or used inside a `Scope` where this restriction is lifted.
2026-02-21 15:05:55 +00:00
Alex Orlenko 151adc0e87 Implement pretty debug format for AnyUserData similar to Value::UserData. 2026-02-20 20:31:29 +00:00
Alex Orlenko 7f3ec63ab5 Support __todebugstring for pretty userdata debug output
Close #681
2026-02-20 18:59:53 +00:00
Alex Orlenko f19c6aac3b Fix tests 2026-02-12 16:20:51 +00:00
Alex Orlenko 29af448ad9 Update README to indicate dev status 2026-02-12 15:28:32 +00:00
55 changed files with 1262 additions and 781 deletions
+2 -2
View File
@@ -1,8 +1,8 @@
name: Documentation (dev)
name: Documentation (main)
on:
push:
branches: [dev]
branches: [main]
workflow_dispatch:
# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
+19
View File
@@ -1,3 +1,22 @@
## v0.12.0-rc.1 (Apr 21, 2026)
- Rust 2024 edition
- Removed `Error::ToLuaConversionError` variant as it was unused (and not practically useful)
- New modules to group data types: `chunk`, `debug`, `error`, `function`, `table`, `string`, `state`, `thread`, `userdata`, `luau`
- Support `__todebugstring` metamethod for pretty formatting userdata value (for debugging)
- New `MaybeSync` trait that is required for userdata types
- Removed lifetime from `BorrowedStr` and `BorrowedBytes`
- New `Thread` methods: `is_resumable`, `is_running`, `is_finished`, `is_error`
- Added `Thread::state` to get raw Lua state pointer
- Luau `TextRequirer` is renamed to `FsRequirer`
- GC interface refactor: `Lua::gc_inc/Lua::gc_gen` is replaced with `gc_set_mode`
- Added `GcIncParams` and `GcGenParams` for GC tuning
- New `UserDataMethods::add_method_once` and `UserDataMethods::add_async_method_once`
- Initial Luau integer64 type support
- Changed interface of `Function::wrap/wrap_mut/wrap_async` to support any Error type
- Changed `AnyUserData::type_name` to return `LuaString` instead
- Added `UserDataOwned<T>` wrapper to take ownership of userdata `T` and implements `FromLua`
## v0.11.6 (Jan 27, 2026)
- Added Lua 5.5 support (`lua55` feature flag)
+5 -5
View File
@@ -1,6 +1,6 @@
[package]
name = "mlua"
version = "0.12.0-dev.1" # remember to update mlua_derive
version = "0.12.0-rc.1" # remember to update mlua_derive
authors = ["Aleksandr Orlenko <zxteam@pm.me>", "kyren <catherine@kyju.org>"]
rust-version = "1.88"
edition = "2024"
@@ -63,7 +63,7 @@ parking_lot = { version = "0.12", features = ["arc_lock"] }
anyhow = { version = "1.0", optional = true }
libc = "0.2"
ffi = { package = "mlua-sys", version = "0.10.0", path = "mlua-sys" }
ffi = { package = "mlua-sys", version = "0.11.0-rc.1", path = "mlua-sys" }
[dev-dependencies]
trybuild = "1.0"
@@ -77,10 +77,10 @@ static_assertions = "1.0"
hyper = { version = "1.2", features = ["full"] }
hyper-util = { version = "0.1.3", features = ["full"] }
http-body-util = "0.1.1"
reqwest = { version = "0.12", features = ["json"] }
reqwest = { version = "0.13", features = ["json"] }
tempfile = "3"
criterion = { version = "0.7", features = ["async_tokio"] }
rustyline = "17.0"
criterion = { version = "0.8", features = ["async_tokio"] }
rustyline = "18.0"
tokio = { version = "1.0", features = ["full"] }
[lints.rust]
+3 -1
View File
@@ -17,6 +17,8 @@
[Benchmarks]: https://github.com/khvzak/script-bench-rs
[FAQ]: FAQ.md
## The main branch is the development version of `mlua`. Please see the [v0.11](https://github.com/mlua-rs/mlua/tree/v0.11) branch for the stable versions of `mlua`.
`mlua` is a set of bindings to the [Lua](https://www.lua.org) programming language for Rust with a goal of providing a
_safe_ (as much as possible), high level, easy to use, practical and flexible API.
@@ -125,7 +127,7 @@ my_project $ LUA_LIB=$HOME/tmp/lua-5.2.4/src LUA_LIB_NAME=lua LUA_LINK=static ca
Just enable the `vendored` feature and cargo will automatically build and link the specified Lua/LuaJIT version. This is the easiest way to get started with `mlua`.
### Standalone mode
In standalone mode, `mlua` allows adding scripting support to your application with a gently configured Lua runtime to ensure safety and soundness.
In standalone mode, `mlua` allows adding scripting support to your application with a properly configured Lua runtime to ensure safety and soundness.
Add to `Cargo.toml`:
+3 -3
View File
@@ -1,6 +1,6 @@
## mlua v0.10 release notes
The v0.10 version of mlua has goal to improve the user experience while keeping the same performance and safety guarantees.
The v0.10 version of mlua has a goal to improve the user experience while keeping the same performance and safety guarantees.
This document highlights the most notable features. For a full list of changes, see the [CHANGELOG].
[CHANGELOG]: https://github.com/mlua-rs/mlua/blob/main/CHANGELOG.md
@@ -40,7 +40,7 @@ assert_eq!(lua.globals().get::<i32>("i")?, 20);
Under the hood, to synchronize access to the Lua state, mlua uses [`ReentrantMutex`] which can be recursively locked by a single thread. Only one thread can execute Lua code at a time, but it's possible to share Lua values between threads.
This has some performance penalties (about 10-20%) compared to the lock free mode. This flag is disabled by default and does not supported in module mode.
This has some performance penalties (about 10-20%) compared to the lock free mode. This flag is disabled by default and is not supported in module mode.
[`ReentrantMutex`]: https://docs.rs/parking_lot/latest/parking_lot/type.ReentrantMutex.html
@@ -144,7 +144,7 @@ The following `Scope` methods were changed:
Instead, scope has comprehensive support for borrowed userdata: `create_any_userdata_ref`, `create_any_userdata_ref_mut`, `create_userdata_ref`, `create_userdata_ref_mut`.
`UserDataRef` and `UserDataRefMut` are no longer acceptable for scoped userdata access as they require owned underlying data.
In mlua v0.9 this can cause read-after-free bug in some edge cases.
In mlua v0.9 this could cause a read-after-free bug in some edge cases.
To temporarily borrow underlying data, the `AnyUserData::borrow_scoped` and `AnyUserData::borrow_mut_scoped` methods were introduced:
+3 -3
View File
@@ -152,9 +152,9 @@ It will automatically trigger JIT compilation for new Lua chunks. To disable it,
#### 1. Better error reporting
When calling a Rust function from Lua and passing wrong arguments, previous mlua versions reported a error message without any context or reference to the particular argument.
When calling a Rust function from Lua and passing wrong arguments, previous mlua versions reported an error message without any context or reference to the particular argument.
In v0.9 it reports a error message with the argument index and expected type:
In v0.9 it reports an error message with the argument index and expected type:
```rust
let func = lua.create_function(|_, _a: i32| Ok(()))?;
@@ -327,7 +327,7 @@ Under the hood a new function `luaopen_alt_module` will be created for the Lua m
- `skip_memory_check` - skip memory allocation checks for some operations.
In module mode, mlua runs in unknown environment and cannot say are there any memory limits or not. As result, some operations that require memory allocation runs in
In module mode, mlua runs in an unknown environment and cannot tell whether there are any memory limits or not. As a result, some operations that require memory allocation run in
protected mode. Setting this attribute will improve performance of such operations with risk of having uncaught exceptions and memory leaks.
#### Improved Windows target
+4 -4
View File
@@ -1,6 +1,6 @@
[package]
name = "mlua-sys"
version = "0.10.0"
version = "0.11.0-rc.1"
authors = ["Aleksandr Orlenko <zxteam@pm.me>"]
rust-version = "1.88"
edition = "2024"
@@ -41,9 +41,9 @@ libc = "0.2"
cc = "1.0"
cfg-if = "1.0"
pkg-config = "0.3.17"
lua-src = { version = ">= 550.0.0, < 550.1.0", optional = true }
luajit-src = { version = ">= 210.6.0, < 210.7.0", optional = true }
luau0-src = { version = "0.18.0", optional = true }
lua-src = { version = ">= 550.1.0, < 550.2.0", optional = true }
luajit-src = { version = ">= 210.7.0, < 210.8.0", optional = true }
luau0-src = { version = "0.20.0", optional = true }
[lints.rust]
unexpected_cfgs = { level = "allow", check-cfg = ['cfg(raw_dylib)'] }
+2
View File
@@ -38,8 +38,10 @@ unsafe extern "C-unwind" {
#[link_name = "luaL_checkinteger"]
pub fn luaL_checkinteger_(L: *mut lua_State, narg: c_int) -> c_int;
pub fn luaL_checkinteger64(L: *mut lua_State, narg: c_int) -> i64;
#[link_name = "luaL_optinteger"]
pub fn luaL_optinteger_(L: *mut lua_State, narg: c_int, def: c_int) -> c_int;
pub fn luaL_optinteger64(L: *mut lua_State, narg: c_int, def: i64) -> i64;
pub fn luaL_checkunsigned(L: *mut lua_State, narg: c_int) -> lua_Unsigned;
pub fn luaL_optunsigned(L: *mut lua_State, narg: c_int, def: lua_Unsigned) -> lua_Unsigned;
+31 -9
View File
@@ -65,14 +65,15 @@ pub const LUA_TBOOLEAN: c_int = 1;
pub const LUA_TLIGHTUSERDATA: c_int = 2;
pub const LUA_TNUMBER: c_int = 3;
pub const LUA_TVECTOR: c_int = 4;
pub const LUA_TINTEGER: c_int = 4;
pub const LUA_TVECTOR: c_int = 5;
pub const LUA_TSTRING: c_int = 5;
pub const LUA_TTABLE: c_int = 6;
pub const LUA_TFUNCTION: c_int = 7;
pub const LUA_TUSERDATA: c_int = 8;
pub const LUA_TTHREAD: c_int = 9;
pub const LUA_TBUFFER: c_int = 10;
pub const LUA_TSTRING: c_int = 6;
pub const LUA_TTABLE: c_int = 7;
pub const LUA_TFUNCTION: c_int = 8;
pub const LUA_TUSERDATA: c_int = 9;
pub const LUA_TTHREAD: c_int = 10;
pub const LUA_TBUFFER: c_int = 11;
/// Guaranteed number of Lua stack slots available to a C function.
pub const LUA_MINSTACK: c_int = 20;
@@ -153,6 +154,7 @@ unsafe extern "C-unwind" {
pub fn lua_tounsignedx(L: *mut lua_State, idx: c_int, isnum: *mut c_int) -> lua_Unsigned;
pub fn lua_tovector(L: *mut lua_State, idx: c_int) -> *const c_float;
pub fn lua_toboolean(L: *mut lua_State, idx: c_int) -> c_int;
pub fn lua_tointeger64(L: *mut lua_State, idx: c_int, isinteger: *mut c_int) -> i64;
pub fn lua_tolstring(L: *mut lua_State, idx: c_int, len: *mut usize) -> *const c_char;
pub fn lua_tostringatom(L: *mut lua_State, idx: c_int, atom: *mut c_int) -> *const c_char;
pub fn lua_tolstringatom(
@@ -182,6 +184,7 @@ unsafe extern "C-unwind" {
pub fn lua_pushnumber(L: *mut lua_State, n: lua_Number);
#[link_name = "lua_pushinteger"]
pub fn lua_pushinteger_(L: *mut lua_State, n: c_int);
pub fn lua_pushinteger64(L: *mut lua_State, n: i64);
pub fn lua_pushunsigned(L: *mut lua_State, n: lua_Unsigned);
#[cfg(not(feature = "luau-vector4"))]
pub fn lua_pushvector(L: *mut lua_State, x: c_float, y: c_float, z: c_float);
@@ -412,6 +415,11 @@ pub unsafe fn lua_isboolean(L: *mut lua_State, n: c_int) -> c_int {
(lua_type(L, n) == LUA_TBOOLEAN) as c_int
}
#[inline(always)]
pub unsafe fn lua_isinteger64(L: *mut lua_State, n: c_int) -> c_int {
(lua_type(L, n) == LUA_TINTEGER) as c_int
}
#[inline(always)]
pub unsafe fn lua_isvector(L: *mut lua_State, n: c_int) -> c_int {
(lua_type(L, n) == LUA_TVECTOR) as c_int
@@ -501,6 +509,12 @@ pub type lua_Coverage = unsafe extern "C-unwind" fn(
size: usize,
);
pub type lua_CounterFunction =
unsafe extern "C-unwind" fn(context: *mut c_void, function: *const c_char, linedefined: c_int);
pub type lua_CounterValue =
unsafe extern "C-unwind" fn(context: *mut c_void, kind: c_int, line: c_int, hits: u64);
unsafe extern "C-unwind" {
pub fn lua_stackdepth(L: *mut lua_State) -> c_int;
pub fn lua_getinfo(L: *mut lua_State, level: c_int, what: *const c_char, ar: *mut lua_Debug) -> c_int;
@@ -515,6 +529,14 @@ unsafe extern "C-unwind" {
pub fn lua_getcoverage(L: *mut lua_State, funcindex: c_int, context: *mut c_void, callback: lua_Coverage);
pub fn lua_getcounters(
L: *mut lua_State,
funcindex: c_int,
context: *mut c_void,
functionvisit: lua_CounterFunction,
countervisit: lua_CounterValue,
);
pub fn lua_debugtrace(L: *mut lua_State) -> *const c_char;
}
@@ -551,8 +573,8 @@ pub struct lua_Callbacks {
/// gets called when L is created (LP == parent) or destroyed (LP == NULL)
pub userthread: Option<unsafe extern "C-unwind" fn(LP: *mut lua_State, L: *mut lua_State)>,
/// gets called when a string is created; returned atom can be retrieved via tostringatom
pub useratom: Option<unsafe extern "C-unwind" fn(s: *const c_char, l: usize) -> i16>,
/// gets called when a string is created to assign an atom id
pub useratom: Option<unsafe extern "C-unwind" fn(L: *mut lua_State, s: *const c_char, l: usize) -> i16>,
/// gets called when BREAK instruction is encountered
pub debugbreak: Option<unsafe extern "C-unwind" fn(L: *mut lua_State, ar: *mut lua_Debug)>,
+1
View File
@@ -80,6 +80,7 @@ unsafe extern "C" {
pub fn luau_set_compile_constant_nil(cons: *mut lua_CompileConstant);
pub fn luau_set_compile_constant_boolean(cons: *mut lua_CompileConstant, b: c_int);
pub fn luau_set_compile_constant_number(cons: *mut lua_CompileConstant, n: f64);
pub fn luau_set_compile_constant_integer64(cons: *mut lua_CompileConstant, l: i64);
pub fn luau_set_compile_constant_vector(cons: *mut lua_CompileConstant, x: f32, y: f32, z: f32, w: f32);
pub fn luau_set_compile_constant_string(cons: *mut lua_CompileConstant, s: *const c_char, l: usize);
}
+2
View File
@@ -14,6 +14,7 @@ pub const LUA_UTF8LIBNAME: *const c_char = cstr!("utf8");
pub const LUA_MATHLIBNAME: *const c_char = cstr!("math");
pub const LUA_DBLIBNAME: *const c_char = cstr!("debug");
pub const LUA_VECLIBNAME: *const c_char = cstr!("vector");
pub const LUA_INTLIBNAME: *const c_char = cstr!("integer");
unsafe extern "C-unwind" {
pub fn luaopen_base(L: *mut lua_State) -> c_int;
@@ -27,6 +28,7 @@ unsafe extern "C-unwind" {
pub fn luaopen_math(L: *mut lua_State) -> c_int;
pub fn luaopen_debug(L: *mut lua_State) -> c_int;
pub fn luaopen_vector(L: *mut lua_State) -> c_int;
pub fn luaopen_integer(L: *mut lua_State) -> c_int;
// open all builtin libraries
pub fn luaL_openlibs(L: *mut lua_State);
+1 -1
View File
@@ -59,7 +59,7 @@ impl Buffer {
/// Returns an adaptor implementing [`io::Read`], [`io::Write`] and [`io::Seek`] over the
/// buffer.
///
/// Buffer operations are infallible, none of the read/write functions will return a Err.
/// Buffer operations are infallible, none of the read/write functions will return an Err.
pub fn cursor(self) -> impl io::Read + io::Write + io::Seek {
BufferCursor(self, 0)
}
+8 -1
View File
@@ -1,3 +1,10 @@
//! Lua chunk loading and execution.
//!
//! This module provides types for loading Lua source code or bytecode into a [`Chunk`],
//! configuring how it is compiled and executed, and converting it into a callable [`Function`].
//!
//! Chunks can be loaded from strings, byte slices, or files via the [`AsChunk`] trait.
use std::borrow::Cow;
use std::collections::HashMap;
use std::ffi::CString;
@@ -153,6 +160,7 @@ pub enum ChunkMode {
/// Represents a constant value that can be used by Luau compiler.
#[cfg(any(feature = "luau", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
#[non_exhaustive]
#[derive(Clone, Debug)]
pub enum CompileConstant {
Nil,
@@ -779,7 +787,6 @@ impl Chunk<'_> {
///
/// The resulted `IntoLua` implementation will convert the chunk into a Lua function without
/// executing it.
#[doc(hidden)]
#[track_caller]
pub fn wrap(chunk: impl AsChunk) -> impl IntoLua {
WrappedChunk {
+36 -35
View File
@@ -4,7 +4,7 @@ use std::ffi::{CStr, CString, OsStr, OsString};
use std::hash::{BuildHasher, Hash};
use std::os::raw::c_int;
use std::path::{Path, PathBuf};
use std::{mem, slice, str};
use std::{slice, str};
use bstr::{BStr, BString, ByteVec};
use num_traits::cast;
@@ -16,7 +16,7 @@ use crate::string::{BorrowedBytes, BorrowedStr, LuaString};
use crate::table::Table;
use crate::thread::Thread;
use crate::traits::{FromLua, IntoLua, ShortTypeName as _};
use crate::types::{Either, LightUserData, MaybeSend, RegistryKey};
use crate::types::{Either, LightUserData, MaybeSend, MaybeSync, RegistryKey};
use crate::userdata::{AnyUserData, UserData};
use crate::value::{Nil, Value};
@@ -86,91 +86,79 @@ impl FromLua for LuaString {
}
}
impl IntoLua for BorrowedStr<'_> {
impl IntoLua for BorrowedStr {
#[inline]
fn into_lua(self, _: &Lua) -> Result<Value> {
Ok(Value::String(self.borrow.into_owned()))
Ok(Value::String(LuaString(self.vref)))
}
#[inline]
unsafe fn push_into_stack(self, lua: &RawLua) -> Result<()> {
lua.push_ref(&self.borrow.0);
lua.push_ref(&self.vref);
Ok(())
}
}
impl IntoLua for &BorrowedStr<'_> {
impl IntoLua for &BorrowedStr {
#[inline]
fn into_lua(self, _: &Lua) -> Result<Value> {
Ok(Value::String(self.borrow.clone().into_owned()))
Ok(Value::String(LuaString(self.vref.clone())))
}
#[inline]
unsafe fn push_into_stack(self, lua: &RawLua) -> Result<()> {
lua.push_ref(&self.borrow.0);
lua.push_ref(&self.vref);
Ok(())
}
}
impl FromLua for BorrowedStr<'_> {
impl FromLua for BorrowedStr {
fn from_lua(value: Value, lua: &Lua) -> Result<Self> {
let s = LuaString::from_lua(value, lua)?;
let BorrowedStr { buf, _lua, .. } = BorrowedStr::try_from(&s)?;
let buf = unsafe { mem::transmute::<&str, &'static str>(buf) };
let borrow = Cow::Owned(s);
Ok(Self { buf, borrow, _lua })
BorrowedStr::try_from(&s)
}
unsafe fn from_stack(idx: c_int, lua: &RawLua) -> Result<Self> {
let s = LuaString::from_stack(idx, lua)?;
let BorrowedStr { buf, _lua, .. } = BorrowedStr::try_from(&s)?;
let buf = unsafe { mem::transmute::<&str, &'static str>(buf) };
let borrow = Cow::Owned(s);
Ok(Self { buf, borrow, _lua })
BorrowedStr::try_from(&s)
}
}
impl IntoLua for BorrowedBytes<'_> {
impl IntoLua for BorrowedBytes {
#[inline]
fn into_lua(self, _: &Lua) -> Result<Value> {
Ok(Value::String(self.borrow.into_owned()))
Ok(Value::String(LuaString(self.vref)))
}
#[inline]
unsafe fn push_into_stack(self, lua: &RawLua) -> Result<()> {
lua.push_ref(&self.borrow.0);
lua.push_ref(&self.vref);
Ok(())
}
}
impl IntoLua for &BorrowedBytes<'_> {
impl IntoLua for &BorrowedBytes {
#[inline]
fn into_lua(self, _: &Lua) -> Result<Value> {
Ok(Value::String(self.borrow.clone().into_owned()))
Ok(Value::String(LuaString(self.vref.clone())))
}
#[inline]
unsafe fn push_into_stack(self, lua: &RawLua) -> Result<()> {
lua.push_ref(&self.borrow.0);
lua.push_ref(&self.vref);
Ok(())
}
}
impl FromLua for BorrowedBytes<'_> {
impl FromLua for BorrowedBytes {
fn from_lua(value: Value, lua: &Lua) -> Result<Self> {
let s = LuaString::from_lua(value, lua)?;
let BorrowedBytes { buf, _lua, .. } = BorrowedBytes::from(&s);
let buf = unsafe { mem::transmute::<&[u8], &'static [u8]>(buf) };
let borrow = Cow::Owned(s);
Ok(Self { buf, borrow, _lua })
Ok(BorrowedBytes::from(&s))
}
unsafe fn from_stack(idx: c_int, lua: &RawLua) -> Result<Self> {
let s = LuaString::from_stack(idx, lua)?;
let BorrowedBytes { buf, _lua, .. } = BorrowedBytes::from(&s);
let buf = unsafe { mem::transmute::<&[u8], &'static [u8]>(buf) };
let borrow = Cow::Owned(s);
Ok(Self { buf, borrow, _lua })
Ok(BorrowedBytes::from(&s))
}
}
@@ -294,7 +282,7 @@ impl FromLua for AnyUserData {
}
}
impl<T: UserData + MaybeSend + 'static> IntoLua for T {
impl<T: UserData + MaybeSend + MaybeSync + 'static> IntoLua for T {
#[inline]
fn into_lua(self, lua: &Lua) -> Result<Value> {
Ok(Value::UserData(lua.create_userdata(self)?))
@@ -535,7 +523,10 @@ impl IntoLua for &str {
impl IntoLua for Cow<'_, str> {
#[inline]
fn into_lua(self, lua: &Lua) -> Result<Value> {
Ok(Value::String(lua.create_string(self.as_bytes())?))
match self {
Cow::Borrowed(s) => s.into_lua(lua),
Cow::Owned(s) => s.into_lua(lua),
}
}
}
@@ -597,7 +588,10 @@ impl IntoLua for &CStr {
impl IntoLua for Cow<'_, CStr> {
#[inline]
fn into_lua(self, lua: &Lua) -> Result<Value> {
Ok(Value::String(lua.create_string(self.to_bytes())?))
match self {
Cow::Borrowed(s) => s.into_lua(lua),
Cow::Owned(s) => s.into_lua(lua),
}
}
}
@@ -818,6 +812,13 @@ macro_rules! lua_convert_int {
});
}
}
#[cfg(feature = "luau")]
if type_id == ffi::LUA_TINTEGER {
let i = ffi::lua_tointeger64(state, idx, std::ptr::null_mut());
return cast(i).ok_or_else(|| {
Error::from_lua_conversion("integer", stringify!($x), "out of range".to_string())
});
}
// Fallback to default
Self::from_lua(lua.stack_value(idx, Some(type_id)), lua.lua())
}
+2 -2
View File
@@ -1,8 +1,8 @@
//! Lua debugging interface.
//!
//! This module provides access to the Lua debug interface, allowing inspection of the call stack,
//! and function information. The main types are [`Debug`] for accessing debug information and
//! [`HookTriggers`] for configuring debug hooks.
//! and function information. The main types are [`struct@Debug`] for accessing debug information
//! and [`HookTriggers`] for configuring debug hooks.
use std::borrow::Cow;
use std::os::raw::c_int;
+12 -5
View File
@@ -1,3 +1,8 @@
//! Lua error handling.
//!
//! This module provides the [`Error`] type returned by all fallible `mlua` operations, together
//! with extension traits for adapting Rust errors for use within Lua.
use std::error::Error as StdError;
use std::fmt;
use std::io::Error as IoError;
@@ -340,7 +345,11 @@ impl Error {
/// Wraps an external error object.
#[inline]
pub fn external<T: Into<Box<DynStdError>>>(err: T) -> Self {
Error::ExternalError(err.into().into())
let boxed = err.into();
match boxed.downcast::<Self>() {
Ok(err) => *err,
Err(boxed) => Error::ExternalError(boxed.into()),
}
}
/// Attempts to downcast the external error object to a concrete type by reference.
@@ -550,10 +559,8 @@ impl<'a> Iterator for Chain<'a> {
#[cfg(test)]
mod assertions {
use super::*;
#[cfg(not(feature = "error-send"))]
static_assertions::assert_not_impl_any!(Error: Send, Sync);
static_assertions::assert_not_impl_any!(super::Error: Send, Sync);
#[cfg(feature = "send")]
static_assertions::assert_impl_all!(Error: Send, Sync);
static_assertions::assert_impl_all!(super::Error: Send, Sync);
}
+107 -19
View File
@@ -3,12 +3,6 @@
//! This module provides types for working with Lua functions from Rust, including
//! both Lua-defined functions and native Rust callbacks.
//!
//! # Main Types
//!
//! - [`Function`] - A handle to a Lua function that can be called from Rust.
//! - [`FunctionInfo`] - Debug information about a function (name, source, line numbers, etc.).
//! - [`CoverageInfo`] - Code coverage data for Luau functions (requires `luau` feature).
//!
//! # Calling Functions
//!
//! Use [`Function::call`] to invoke a Lua function synchronously:
@@ -81,12 +75,13 @@
use std::cell::RefCell;
use std::os::raw::{c_int, c_void};
use std::result::Result as StdResult;
use std::{mem, ptr, slice};
use crate::error::{Error, Result};
use crate::error::{Error, ExternalError, ExternalResult, Result};
use crate::state::Lua;
use crate::table::Table;
use crate::traits::{FromLuaMulti, IntoLua, IntoLuaMulti, LuaNativeFn, LuaNativeFnMut};
use crate::traits::{FromLuaMulti, IntoLua, IntoLuaMulti};
use crate::types::{Callback, LuaType, MaybeSend, ValueRef};
use crate::util::{
StackGuard, assert_stack, check_stack, linenumber_to_usize, pop_error, ptr_to_lossy_str, ptr_to_str,
@@ -96,7 +91,6 @@ use crate::value::Value;
#[cfg(feature = "async")]
use {
crate::thread::AsyncThread,
crate::traits::LuaNativeAsyncFn,
crate::types::AsyncCallback,
std::future::{self, Future},
std::pin::{Pin, pin},
@@ -246,7 +240,7 @@ impl Function {
/// # }
/// ```
///
/// [`AsyncThread`]: crate::AsyncThread
/// [`AsyncThread`]: crate::thread::AsyncThread
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
pub fn call_async<R>(&self, args: impl IntoLuaMulti) -> AsyncCallFuture<R>
@@ -636,30 +630,32 @@ impl Function {
/// Wraps a Rust function or closure, returning an opaque type that implements [`IntoLua`]
/// trait.
#[inline]
pub fn wrap<F, A, R>(func: F) -> impl IntoLua
pub fn wrap<F, A, R, E>(func: F) -> impl IntoLua
where
F: LuaNativeFn<A, Output = Result<R>> + MaybeSend + 'static,
F: LuaNativeFn<A, Output = StdResult<R, E>> + MaybeSend + 'static,
A: FromLuaMulti,
R: IntoLuaMulti,
E: ExternalError,
{
WrappedFunction(Box::new(move |lua, nargs| unsafe {
let args = A::from_stack_args(nargs, 1, None, lua)?;
func.call(args)?.push_into_stack_multi(lua)
func.call(args).into_lua_err()?.push_into_stack_multi(lua)
}))
}
/// Wraps a Rust mutable closure, returning an opaque type that implements [`IntoLua`] trait.
pub fn wrap_mut<F, A, R>(func: F) -> impl IntoLua
pub fn wrap_mut<F, A, R, E>(func: F) -> impl IntoLua
where
F: LuaNativeFnMut<A, Output = Result<R>> + MaybeSend + 'static,
F: LuaNativeFnMut<A, Output = StdResult<R, E>> + MaybeSend + 'static,
A: FromLuaMulti,
R: IntoLuaMulti,
E: ExternalError,
{
let func = RefCell::new(func);
WrappedFunction(Box::new(move |lua, nargs| unsafe {
let mut func = func.try_borrow_mut().map_err(|_| Error::RecursiveMutCallback)?;
let args = A::from_stack_args(nargs, 1, None, lua)?;
func.call(args)?.push_into_stack_multi(lua)
func.call(args).into_lua_err()?.push_into_stack_multi(lua)
}))
}
@@ -672,6 +668,7 @@ impl Function {
pub fn wrap_raw<F, A>(func: F) -> impl IntoLua
where
F: LuaNativeFn<A> + MaybeSend + 'static,
F::Output: IntoLuaMulti,
A: FromLuaMulti,
{
WrappedFunction(Box::new(move |lua, nargs| unsafe {
@@ -688,6 +685,7 @@ impl Function {
pub fn wrap_raw_mut<F, A>(func: F) -> impl IntoLua
where
F: LuaNativeFnMut<A> + MaybeSend + 'static,
F::Output: IntoLuaMulti,
A: FromLuaMulti,
{
let func = RefCell::new(func);
@@ -702,11 +700,12 @@ impl Function {
/// trait.
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
pub fn wrap_async<F, A, R>(func: F) -> impl IntoLua
pub fn wrap_async<F, A, R, E>(func: F) -> impl IntoLua
where
F: LuaNativeAsyncFn<A, Output = Result<R>> + MaybeSend + 'static,
F: LuaNativeAsyncFn<A, Output = StdResult<R, E>> + MaybeSend + 'static,
A: FromLuaMulti,
R: IntoLuaMulti,
E: ExternalError,
{
WrappedAsyncFunction(Box::new(move |rawlua, nargs| unsafe {
let args = match A::from_stack_args(nargs, 1, None, rawlua) {
@@ -715,7 +714,7 @@ impl Function {
};
let lua = rawlua.lua();
let fut = func.call(args);
Box::pin(async move { fut.await?.push_into_stack_multi(lua.raw_lua()) })
Box::pin(async move { fut.await.into_lua_err()?.push_into_stack_multi(lua.raw_lua()) })
}))
}
@@ -729,6 +728,7 @@ impl Function {
pub fn wrap_raw_async<F, A>(func: F) -> impl IntoLua
where
F: LuaNativeAsyncFn<A> + MaybeSend + 'static,
F::Output: IntoLuaMulti,
A: FromLuaMulti,
{
WrappedAsyncFunction(Box::new(move |rawlua, nargs| unsafe {
@@ -788,6 +788,94 @@ impl<R: FromLuaMulti> Future for AsyncCallFuture<R> {
}
}
/// A trait for types that can be used as Lua functions.
pub trait LuaNativeFn<A: FromLuaMulti> {
type Output;
fn call(&self, args: A) -> Self::Output;
}
/// A trait for types with mutable state that can be used as Lua functions.
pub trait LuaNativeFnMut<A: FromLuaMulti> {
type Output;
fn call(&mut self, args: A) -> Self::Output;
}
/// A trait for types that returns a future and can be used as Lua functions.
#[cfg(feature = "async")]
pub trait LuaNativeAsyncFn<A: FromLuaMulti> {
type Output;
fn call(&self, args: A) -> impl Future<Output = Self::Output> + MaybeSend + 'static;
}
macro_rules! impl_lua_native_fn {
($($A:ident),*) => {
impl<FN, $($A,)* R> LuaNativeFn<($($A,)*)> for FN
where
FN: Fn($($A,)*) -> R + MaybeSend + 'static,
($($A,)*): FromLuaMulti,
{
type Output = R;
#[allow(non_snake_case)]
fn call(&self, args: ($($A,)*)) -> Self::Output {
let ($($A,)*) = args;
self($($A,)*)
}
}
impl<FN, $($A,)* R> LuaNativeFnMut<($($A,)*)> for FN
where
FN: FnMut($($A,)*) -> R + MaybeSend + 'static,
($($A,)*): FromLuaMulti,
{
type Output = R;
#[allow(non_snake_case)]
fn call(&mut self, args: ($($A,)*)) -> Self::Output {
let ($($A,)*) = args;
self($($A,)*)
}
}
#[cfg(feature = "async")]
impl<FN, $($A,)* Fut, R> LuaNativeAsyncFn<($($A,)*)> for FN
where
FN: Fn($($A,)*) -> Fut + MaybeSend + 'static,
($($A,)*): FromLuaMulti,
Fut: Future<Output = R> + MaybeSend + 'static,
{
type Output = R;
#[allow(non_snake_case)]
fn call(&self, args: ($($A,)*)) -> impl Future<Output = Self::Output> + MaybeSend + 'static {
let ($($A,)*) = args;
self($($A,)*)
}
}
};
}
impl_lua_native_fn!();
impl_lua_native_fn!(A);
impl_lua_native_fn!(A, B);
impl_lua_native_fn!(A, B, C);
impl_lua_native_fn!(A, B, C, D);
impl_lua_native_fn!(A, B, C, D, E);
impl_lua_native_fn!(A, B, C, D, E, F);
impl_lua_native_fn!(A, B, C, D, E, F, G);
impl_lua_native_fn!(A, B, C, D, E, F, G, H);
impl_lua_native_fn!(A, B, C, D, E, F, G, H, I);
impl_lua_native_fn!(A, B, C, D, E, F, G, H, I, J);
impl_lua_native_fn!(A, B, C, D, E, F, G, H, I, J, K);
impl_lua_native_fn!(A, B, C, D, E, F, G, H, I, J, K, L);
impl_lua_native_fn!(A, B, C, D, E, F, G, H, I, J, K, L, M);
impl_lua_native_fn!(A, B, C, D, E, F, G, H, I, J, K, L, M, N);
impl_lua_native_fn!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O);
impl_lua_native_fn!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P);
#[cfg(test)]
mod assertions {
use super::*;
+56 -39
View File
@@ -61,6 +61,7 @@
//! [`Future`]: std::future::Future
//! [`serde::Serialize`]: https://docs.serde.rs/serde/ser/trait.Serialize.html
//! [`serde::Deserialize`]: https://docs.serde.rs/serde/de/trait.Deserialize.html
//! [`AsyncThread`]: crate::thread::AsyncThread
// Deny warnings inside doc tests / examples. When this isn't present, rustdoc doesn't show *any*
// warnings at all.
@@ -72,77 +73,93 @@
mod macros;
mod buffer;
mod chunk;
mod conversion;
mod error;
#[cfg(any(feature = "luau", doc))]
mod luau;
mod memory;
mod multi;
mod scope;
mod state;
mod stdlib;
mod string;
mod thread;
mod traits;
mod types;
mod userdata;
mod util;
mod value;
mod vector;
pub mod chunk;
pub mod debug;
pub mod error;
pub mod function;
#[cfg(any(feature = "luau", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
pub mod luau;
pub mod prelude;
pub mod state;
pub mod string;
pub mod table;
pub mod thread;
pub mod userdata;
pub use bstr::BString;
pub use ffi::{self, lua_CFunction, lua_State};
pub use crate::chunk::{AsChunk, Chunk, ChunkMode};
pub use crate::error::{Error, ErrorContext, ExternalError, ExternalResult, Result};
#[doc(inline)]
pub use crate::error::{Error, Result};
#[doc(inline)]
pub use crate::function::Function;
pub use crate::multi::{MultiValue, Variadic};
pub use crate::scope::Scope;
pub use crate::state::{GCMode, Lua, LuaOptions, WeakLua};
#[doc(inline)]
pub use crate::state::{Lua, LuaOptions, WeakLua};
pub use crate::stdlib::StdLib;
pub use crate::string::{BorrowedBytes, BorrowedStr, LuaString, LuaString as String};
#[doc(inline)]
pub use crate::string::{BorrowedBytes, BorrowedStr, LuaString};
#[doc(inline)]
pub use crate::table::Table;
pub use crate::thread::{Thread, ThreadStatus};
pub use crate::traits::{
FromLua, FromLuaMulti, IntoLua, IntoLuaMulti, LuaNativeFn, LuaNativeFnMut, ObjectLike,
};
#[doc(inline)]
pub use crate::thread::Thread;
#[doc(inline)]
pub use crate::traits::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti, ObjectLike};
pub use crate::types::{
AppDataRef, AppDataRefMut, Either, Integer, LightUserData, MaybeSend, Number, RegistryKey, VmState,
};
pub use crate::userdata::{
AnyUserData, MetaMethod, UserData, UserDataFields, UserDataMetatable, UserDataMethods, UserDataRef,
UserDataRefMut, UserDataRegistry,
AppDataRef, AppDataRefMut, Either, Integer, LightUserData, MaybeSend, MaybeSync, Number, RegistryKey,
VmState,
};
#[doc(inline)]
pub use crate::userdata::AnyUserData;
pub use crate::value::{Nil, Value};
// Re-export some types to keep backward compatibility and avoid breaking changes in the public API.
#[doc(hidden)]
pub use crate::chunk::{AsChunk, Chunk, ChunkMode};
#[cfg(feature = "luau")]
#[doc(hidden)]
pub use crate::chunk::{CompileConstant, Compiler};
#[doc(hidden)]
pub use crate::error::{ErrorContext, ExternalError, ExternalResult};
#[doc(hidden)]
pub use crate::string::LuaString as String;
#[doc(hidden)]
pub use crate::table::{TablePairs, TableSequence};
#[doc(hidden)]
pub use crate::thread::ThreadStatus;
#[doc(hidden)]
pub use crate::userdata::{
MetaMethod, UserData, UserDataFields, UserDataMetatable, UserDataMethods, UserDataOwned, UserDataRef,
UserDataRefMut, UserDataRegistry,
};
#[cfg(not(feature = "luau"))]
#[doc(inline)]
pub use crate::debug::HookTriggers;
#[cfg(any(feature = "luau", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
pub use crate::{
buffer::Buffer,
chunk::{CompileConstant, Compiler},
luau::{HeapDump, NavigateError, Require, TextRequirer},
vector::Vector,
};
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
pub use crate::{thread::AsyncThread, traits::LuaNativeAsyncFn};
pub use crate::{buffer::Buffer, vector::Vector};
#[cfg(feature = "serde")]
#[doc(hidden)]
pub use crate::serde::{DeserializeOptions, SerializeOptions};
#[cfg(feature = "serde")]
#[doc(inline)]
pub use crate::{
serde::{LuaSerdeExt, de::Options as DeserializeOptions, ser::Options as SerializeOptions},
value::SerializableValue,
};
pub use crate::{serde::LuaSerdeExt, value::SerializableValue};
#[cfg(feature = "serde")]
#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
@@ -243,10 +260,10 @@ pub use mlua_derive::FromLua;
///
/// * skip_memory_check - skip memory allocation checks for some operations.
///
/// In module mode, mlua runs in unknown environment and cannot say are there any memory
/// limits or not. As result, some operations that require memory allocation runs in
/// protected mode. Setting this attribute will improve performance of such operations
/// with risk of having uncaught exceptions and memory leaks.
/// In module mode, mlua runs in an unknown environment and cannot tell whether there are any memory
/// limits or not. As a result, some operations that require memory allocation run in protected
/// mode. Setting this attribute will improve performance of such operations with risk of having
/// uncaught exceptions and memory leaks.
///
/// ```ignore
/// #[mlua::lua_module(skip_memory_check)]
+9 -2
View File
@@ -1,3 +1,10 @@
//! Luau-specific extensions and types.
//!
//! This module provides Luau-specific functionality including custom [`require`] implementations,
//! heap memory analysis, and Luau VM integration utilities.
//!
//! [`require`]: crate::Lua::create_require_function
use std::ffi::{CStr, CString};
use std::os::raw::c_int;
use std::ptr;
@@ -10,7 +17,7 @@ use crate::traits::{FromLuaMulti, IntoLua};
use crate::types::MaybeSend;
pub use heap_dump::HeapDump;
pub use require::{NavigateError, Require, TextRequirer};
pub use require::{FsRequirer, NavigateError, Require};
// Since Luau has some missing standard functions, we re-implement them here
@@ -86,7 +93,7 @@ impl Lua {
}
// Enable default `require` implementation
let require = self.create_require_function(require::TextRequirer::new())?;
let require = self.create_require_function(FsRequirer::new())?;
self.globals().raw_set("require", require)?;
Ok(())
+1 -2
View File
@@ -12,8 +12,7 @@ use crate::state::{Lua, callback_error_ext};
use crate::table::Table;
use crate::types::MaybeSend;
// TODO: Rename to FsRequirer
pub use fs::TextRequirer;
pub use fs::FsRequirer;
/// An error that can occur during navigation in the Luau `require-by-string` system.
#[derive(Debug, Clone)]
+6 -6
View File
@@ -12,7 +12,7 @@ use super::{NavigateError, Require};
/// The standard implementation of Luau `require-by-string` navigation.
#[derive(Default, Debug)]
pub struct TextRequirer {
pub struct FsRequirer {
/// An absolute path to the current Luau module (not mapped to a physical file)
abs_path: PathBuf,
/// A relative path to the current Luau module (not mapped to a physical file)
@@ -22,7 +22,7 @@ pub struct TextRequirer {
resolved_path: Option<PathBuf>,
}
impl TextRequirer {
impl FsRequirer {
/// The prefix used for chunk names in the require system.
/// Only chunk names starting with this prefix are allowed to be used in `require`.
const CHUNK_PREFIX: &str = "@";
@@ -36,7 +36,7 @@ impl TextRequirer {
/// The filename for the Luau configuration file.
const LUAU_CONFIG_FILENAME: &str = ".config.luau";
/// Creates a new `TextRequirer` instance.
/// Creates a new `FsRequirer` instance.
pub fn new() -> Self {
Self::default()
}
@@ -114,7 +114,7 @@ impl TextRequirer {
}
}
impl Require for TextRequirer {
impl Require for FsRequirer {
fn is_require_allowed(&self, chunk_name: &str) -> bool {
chunk_name.starts_with(Self::CHUNK_PREFIX)
}
@@ -231,7 +231,7 @@ impl Require for TextRequirer {
mod tests {
use std::path::Path;
use super::TextRequirer;
use super::FsRequirer;
#[test]
fn test_path_normalize() {
@@ -267,7 +267,7 @@ mod tests {
// '..' disappears if path is absolute and component is non-erasable
("/../", "/"),
] {
let path = TextRequirer::normalize_path(input.as_ref());
let path = FsRequirer::normalize_path(input.as_ref());
assert_eq!(
&path,
expected.as_ref() as &Path,
+24 -14
View File
@@ -3,34 +3,44 @@
#[doc(no_inline)]
pub use crate::{
AnyUserData as LuaAnyUserData, BorrowedBytes as LuaBorrowedBytes, BorrowedStr as LuaBorrowedStr,
Chunk as LuaChunk, Either as LuaEither, Error as LuaError, ErrorContext as LuaErrorContext,
ExternalError as LuaExternalError, ExternalResult as LuaExternalResult, FromLua, FromLuaMulti,
Function as LuaFunction, GCMode as LuaGCMode, Integer as LuaInteger, IntoLua, IntoLuaMulti,
LightUserData as LuaLightUserData, Lua, LuaNativeFn, LuaNativeFnMut, LuaOptions, LuaString,
MetaMethod as LuaMetaMethod, MultiValue as LuaMultiValue, Nil as LuaNil, Number as LuaNumber,
Either as LuaEither, Error as LuaError, FromLua, FromLuaMulti, Function as LuaFunction,
Integer as LuaInteger, IntoLua, IntoLuaMulti, LightUserData as LuaLightUserData, Lua, LuaOptions,
LuaString, MetaMethod as LuaMetaMethod, MultiValue as LuaMultiValue, Nil as LuaNil, Number as LuaNumber,
ObjectLike as LuaObjectLike, RegistryKey as LuaRegistryKey, Result as LuaResult, StdLib as LuaStdLib,
Table as LuaTable, Thread as LuaThread, ThreadStatus as LuaThreadStatus, UserData as LuaUserData,
UserDataFields as LuaUserDataFields, UserDataMetatable as LuaUserDataMetatable,
UserDataMethods as LuaUserDataMethods, UserDataRef as LuaUserDataRef,
UserDataRefMut as LuaUserDataRefMut, UserDataRegistry as LuaUserDataRegistry, Value as LuaValue,
Variadic as LuaVariadic, VmState as LuaVmState, WeakLua, function::FunctionInfo as LuaFunctionInfo,
table::TablePairs as LuaTablePairs, table::TableSequence as LuaTableSequence,
Table as LuaTable, Thread as LuaThread, UserData as LuaUserData, UserDataFields as LuaUserDataFields,
UserDataMetatable as LuaUserDataMetatable, UserDataMethods as LuaUserDataMethods,
UserDataOwned as LuaUserDataOwned, UserDataRef as LuaUserDataRef, UserDataRefMut as LuaUserDataRefMut,
UserDataRegistry as LuaUserDataRegistry, Value as LuaValue, Variadic as LuaVariadic,
VmState as LuaVmState, WeakLua, chunk::AsChunk as AsLuaChunk, chunk::Chunk as LuaChunk,
chunk::ChunkMode as LuaChunkMode, error::ErrorContext as LuaErrorContext,
error::ExternalError as LuaExternalError, error::ExternalResult as LuaExternalResult,
function::FunctionInfo as LuaFunctionInfo, function::LuaNativeFn, function::LuaNativeFnMut,
state::GcIncParams as LuaGcIncParams, state::GcMode as LuaGcMode, table::TablePairs as LuaTablePairs,
table::TableSequence as LuaTableSequence, thread::ThreadStatus as LuaThreadStatus,
};
#[cfg(not(feature = "luau"))]
#[doc(no_inline)]
pub use crate::HookTriggers as LuaHookTriggers;
#[cfg(any(feature = "lua54", feature = "lua55"))]
#[doc(no_inline)]
pub use crate::state::GcGenParams as LuaGcGenParams;
#[cfg(feature = "luau")]
#[doc(no_inline)]
pub use crate::{
CompileConstant as LuaCompileConstant, NavigateError as LuaNavigateError, Require as LuaRequire,
TextRequirer as LuaTextRequirer, Vector as LuaVector,
Vector as LuaVector,
chunk::{CompileConstant as LuaCompileConstant, Compiler as LuaCompiler},
luau::{
FsRequirer as LuaFsRequirer, HeapDump as LuaHeapDump, NavigateError as LuaNavigateError,
Require as LuaRequire,
},
};
#[cfg(feature = "async")]
#[doc(no_inline)]
pub use crate::{AsyncThread as LuaAsyncThread, LuaNativeAsyncFn};
pub use crate::{function::LuaNativeAsyncFn, thread::AsyncThread as LuaAsyncThread};
#[cfg(feature = "serde")]
#[doc(no_inline)]
+4 -6
View File
@@ -37,8 +37,8 @@ pub trait LuaSerdeExt: Sealed {
fn null(&self) -> Value;
/// A metatable attachable to a Lua table to systematically encode it as Array (instead of Map).
/// As result, encoded Array will contain only sequence part of the table, with the same length
/// as the `#` operator on that table.
/// As a result, encoded Array will contain only sequence part of the table, with the same
/// length as the `#` operator on that table.
///
/// # Example
///
@@ -242,7 +242,5 @@ static ARRAY_METATABLE_REGISTRY_KEY: u8 = 0;
pub mod de;
pub mod ser;
#[doc(inline)]
pub use de::Deserializer;
#[doc(inline)]
pub use ser::Serializer;
pub use de::{Deserializer, Options as DeserializeOptions};
pub use ser::{Options as SerializeOptions, Serializer};
+246 -187
View File
@@ -1,3 +1,8 @@
//! Lua state management.
//!
//! This module provides the main [`Lua`] state handle together with state-specific
//! configuration and garbage collector controls.
use std::any::TypeId;
use std::cell::{BorrowError, BorrowMutError, RefCell};
use std::marker::PhantomData;
@@ -20,8 +25,8 @@ use crate::table::Table;
use crate::thread::Thread;
use crate::traits::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti};
use crate::types::{
AppDataRef, AppDataRefMut, ArcReentrantMutexGuard, Integer, LuaType, MaybeSend, Number, ReentrantMutex,
ReentrantMutexGuard, RegistryKey, VmState, XRc, XWeak,
AppDataRef, AppDataRefMut, ArcReentrantMutexGuard, Integer, LuaType, MaybeSend, MaybeSync, Number,
ReentrantMutex, ReentrantMutexGuard, RegistryKey, VmState, XRc, XWeak,
};
use crate::userdata::{AnyUserData, UserData, UserDataProxy, UserDataRegistry, UserDataStorage};
use crate::util::{StackGuard, assert_stack, check_stack, protect_lua_closure, push_string, rawset_field};
@@ -44,6 +49,7 @@ use {
use serde::Serialize;
pub(crate) use extra::ExtraData;
#[doc(hidden)]
pub use raw::RawLua;
pub(crate) use util::callback_error_ext;
@@ -62,20 +68,126 @@ pub struct WeakLua(XWeak<ReentrantMutex<RawLua>>);
pub(crate) struct LuaGuard(ArcReentrantMutexGuard<RawLua>);
/// Mode of the Lua garbage collector (GC).
///
/// In Lua 5.4 GC can work in two modes: incremental and generational.
/// Previous Lua versions support only incremental GC.
/// Tuning parameters for the incremental GC collector.
///
/// More information can be found in the Lua [documentation].
///
/// [documentation]: https://www.lua.org/manual/5.4/manual.html#2.5
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum GCMode {
Incremental,
/// [documentation]: https://www.lua.org/manual/5.5/manual.html#2.5.1
#[non_exhaustive]
#[derive(Clone, Copy, Debug, Default)]
pub struct GcIncParams {
/// Pause between successive GC cycles, expressed as a percentage of live memory.
#[cfg(not(feature = "luau"))]
#[cfg_attr(docsrs, doc(cfg(not(feature = "luau"))))]
pub pause: Option<c_int>,
/// Target heap size as a percentage of live data, controlling how aggressively
/// the GC reclaims memory (`LUA_GCSETGOAL`).
#[cfg(any(feature = "luau", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
pub goal: Option<c_int>,
/// GC work performed per unit of memory allocated.
pub step_multiplier: Option<c_int>,
/// Granularity of each GC step (see Lua reference for details).
#[cfg(any(feature = "lua55", feature = "lua54", feature = "luau"))]
#[cfg_attr(docsrs, doc(cfg(any(feature = "lua55", feature = "lua54", feature = "luau"))))]
pub step_size: Option<c_int>,
}
impl GcIncParams {
/// Sets the `pause` parameter.
#[cfg(not(feature = "luau"))]
#[cfg_attr(docsrs, doc(cfg(not(feature = "luau"))))]
pub fn pause(mut self, v: c_int) -> Self {
self.pause = Some(v);
self
}
/// Sets the `goal` parameter.
#[cfg(any(feature = "luau", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
pub fn goal(mut self, v: c_int) -> Self {
self.goal = Some(v);
self
}
/// Sets the `step_multiplier` parameter.
pub fn step_multiplier(mut self, v: c_int) -> Self {
self.step_multiplier = Some(v);
self
}
/// Sets the `step_size` parameter.
#[cfg(any(feature = "lua55", feature = "lua54", feature = "luau"))]
#[cfg_attr(docsrs, doc(cfg(any(feature = "lua55", feature = "lua54", feature = "luau"))))]
pub fn step_size(mut self, v: c_int) -> Self {
self.step_size = Some(v);
self
}
}
/// Tuning parameters for the generational GC collector (Lua 5.4+).
///
/// More information can be found in the Lua [documentation].
///
/// [documentation]: https://www.lua.org/manual/5.5/manual.html#2.5.2
#[cfg(any(feature = "lua55", feature = "lua54"))]
#[cfg_attr(docsrs, doc(cfg(any(feature = "lua55", feature = "lua54"))))]
#[non_exhaustive]
#[derive(Clone, Copy, Debug, Default)]
pub struct GcGenParams {
/// Frequency of minor (young-generation) collection steps.
pub minor_multiplier: Option<c_int>,
/// Threshold controlling how large the young generation can grow before triggering
/// a shift from minor to major collection.
pub minor_to_major: Option<c_int>,
/// Threshold controlling how much the major collection must shrink the heap before
/// switching back to minor (young-generation) collection.
#[cfg(feature = "lua55")]
#[cfg_attr(docsrs, doc(cfg(feature = "lua55")))]
pub major_to_minor: Option<c_int>,
}
#[cfg(any(feature = "lua55", feature = "lua54"))]
impl GcGenParams {
/// Sets the `minor_multiplier` parameter.
pub fn minor_multiplier(mut self, v: c_int) -> Self {
self.minor_multiplier = Some(v);
self
}
/// Sets the `minor_to_major` threshold.
pub fn minor_to_major(mut self, v: c_int) -> Self {
self.minor_to_major = Some(v);
self
}
/// Sets the `major_to_minor` parameter.
#[cfg(feature = "lua55")]
#[cfg_attr(docsrs, doc(cfg(feature = "lua55")))]
pub fn major_to_minor(mut self, v: c_int) -> Self {
self.major_to_minor = Some(v);
self
}
}
/// Lua garbage collector (GC) operating mode.
///
/// Use [`Lua::gc_set_mode`] to switch the collector mode and/or tune its parameters.
#[non_exhaustive]
#[derive(Clone, Debug)]
pub enum GcMode {
/// Incremental mark-and-sweep
Incremental(GcIncParams),
/// Generational
#[cfg(any(feature = "lua55", feature = "lua54"))]
#[cfg_attr(docsrs, doc(cfg(any(feature = "lua55", feature = "lua54"))))]
Generational,
Generational(GcGenParams),
}
/// Controls Lua interpreter behavior such as Rust panics handling.
@@ -337,30 +449,27 @@ impl Lua {
R::from_stack_multi(nresults, &lua)
}
/// Runs callback with the inner RawLua value. It can be used to manually push and get values on
/// the stack.
/// Calls provided function passing a reference to the [`RawLua`] handle.
///
/// This function is safe because all unsafe actions with RawLua can only be done with unsafe
/// Provided [`RawLua`] handle can be used to manually pushing/popping values to/from the stack.
///
/// # Example
/// ```
/// # use mlua::{Lua, Result, FromLua, IntoLua};
/// # use mlua::{Lua, Result, FromLua, IntoLua, IntoLuaMulti};
/// # fn main() -> Result<()> {
/// let lua = Lua::new();
/// let n: i32 = {
/// let num = 11i32;
/// lua.exec_raw_lua(|lua| {
/// unsafe {
/// <i32 as IntoLua>::push_into_stack(num, lua)?;
/// let nums = (3, 4, 5);
/// lua.exec_raw_lua(|rawlua| unsafe {
/// nums.push_into_stack_multi(rawlua)?;
/// let mut sum = 0;
/// for _ in 0..3 {
/// sum += rawlua.pop::<i32>()?;
/// }
///
/// let n = unsafe {
/// <i32 as FromLua>::from_stack(-1, lua)?
/// };
/// Result::Ok(n)
/// Result::Ok(sum)
/// })
/// }?;
/// assert_eq!(n, 11);
/// assert_eq!(n, 12);
/// # Ok(())
/// # }
/// ```
@@ -437,31 +546,6 @@ impl Lua {
Ok(())
}
#[doc(hidden)]
#[deprecated(since = "0.11.0", note = "Use `register_module` instead")]
#[cfg(not(feature = "luau"))]
#[cfg(not(tarpaulin_include))]
pub fn load_from_function<T: FromLua>(&self, modname: &str, func: Function) -> Result<T> {
let loaded = unsafe {
self.exec_raw::<Table>((), |state| {
ffi::luaL_getsubtable(state, ffi::LUA_REGISTRYINDEX, ffi::LUA_LOADED_TABLE);
})?
};
let value = match loaded.raw_get(modname)? {
Value::Nil => {
let result = match func.call(modname)? {
Value::Nil => Value::Boolean(true),
res => res,
};
loaded.raw_set(modname, &result)?;
result
}
res => res,
};
T::from_lua(value, self)
}
/// Unloads module `modname`.
///
/// This method does not support unloading binary Lua modules since they are internally cached
@@ -909,8 +993,8 @@ impl Lua {
/// Gets information about the interpreter runtime stack at the given level.
///
/// This function calls callback `f`, passing the [`Debug`] structure that can be used to get
/// information about the function executing at a given level.
/// This function calls callback `f`, passing the [`struct@Debug`] structure that can be used to
/// get information about the function executing at a given level.
/// Level `0` is the current running function, whereas level `n+1` is the function that has
/// called level `n` (except for tail calls, which do not count in the stack).
pub fn inspect_stack<R>(&self, level: usize, f: impl FnOnce(&Debug) -> R) -> Option<R> {
@@ -998,19 +1082,19 @@ impl Lua {
unsafe { ffi::lua_gc(lua.main_state(), ffi::LUA_GCISRUNNING, 0) != 0 }
}
/// Stop the Lua GC from running
/// Stops the Lua GC from running.
pub fn gc_stop(&self) {
let lua = self.lock();
unsafe { ffi::lua_gc(lua.main_state(), ffi::LUA_GCSTOP, 0) };
}
/// Restarts the Lua GC if it is not running
/// Restarts the Lua GC if it is not running.
pub fn gc_restart(&self) {
let lua = self.lock();
unsafe { ffi::lua_gc(lua.main_state(), ffi::LUA_GCRESTART, 0) };
}
/// Perform a full garbage-collection cycle.
/// Performs a full garbage-collection cycle.
///
/// It may be necessary to call this function twice to collect all currently unreachable
/// objects. Once to finish the current gc cycle, and once to start and finish the next cycle.
@@ -1023,153 +1107,128 @@ impl Lua {
}
}
/// Steps the garbage collector one indivisible step.
/// Performs a basic step of garbage collection.
///
/// Returns `true` if this has finished a collection cycle.
/// In incremental mode, a basic step corresponds to the current step size. In generational
/// mode, a basic step performs a full minor collection or an incremental step, if the collector
/// has scheduled one.
///
/// In incremental mode, returns `true` if this step has finished a collection cycle.
/// In generational mode, returns `true` if the step finished a major collection.
pub fn gc_step(&self) -> Result<bool> {
self.gc_step_kbytes(0)
}
/// Steps the garbage collector as though memory had been allocated.
///
/// if `kbytes` is 0, then this is the same as calling `gc_step`. Returns true if this step has
/// finished a collection cycle.
pub fn gc_step_kbytes(&self, kbytes: c_int) -> Result<bool> {
let lua = self.lock();
let state = lua.main_state();
unsafe {
check_stack(state, 3)?;
protect_lua!(state, 0, 0, |state| {
ffi::lua_gc(state, ffi::LUA_GCSTEP, kbytes) != 0
ffi::lua_gc(state, ffi::LUA_GCSTEP, 0) != 0
})
}
}
/// Sets the `pause` value of the collector.
/// Switches the GC to the given mode with the provided parameters.
///
/// Returns the previous value of `pause`. More information can be found in the Lua
/// [documentation].
/// Returns the previous [`GcMode`]. The returned value's parameter fields are always
/// `None` because Lua's C API does not provide a way to read back current parameter values
/// without changing them.
///
/// For Luau this parameter sets GC goal
/// # Examples
///
/// [documentation]: https://www.lua.org/manual/5.4/manual.html#2.5
pub fn gc_set_pause(&self, pause: c_int) -> c_int {
/// Switch to generational mode (Lua 5.4+):
/// ```ignore
/// let prev = lua.gc_set_mode(GcMode::Generational(GcGenParams::default()));
/// ```
///
/// Switch to incremental mode with custom parameters:
/// ```ignore
/// lua.gc_set_mode(GcMode::Incremental(
/// GcIncParams::default().pause(200).step_multiplier(100)
/// ));
/// ```
pub fn gc_set_mode(&self, mode: GcMode) -> GcMode {
let lua = self.lock();
let state = lua.main_state();
unsafe {
match mode {
#[cfg(feature = "lua55")]
return ffi::lua_gc(state, ffi::LUA_GCPARAM, ffi::LUA_GCPPAUSE, pause);
#[cfg(not(any(feature = "lua55", feature = "luau")))]
return ffi::lua_gc(state, ffi::LUA_GCSETPAUSE, pause);
GcMode::Incremental(params) => unsafe {
if let Some(v) = params.pause {
ffi::lua_gc(state, ffi::LUA_GCPARAM, ffi::LUA_GCPPAUSE, v);
}
if let Some(v) = params.step_multiplier {
ffi::lua_gc(state, ffi::LUA_GCPARAM, ffi::LUA_GCPSTEPMUL, v);
}
if let Some(v) = params.step_size {
ffi::lua_gc(state, ffi::LUA_GCPARAM, ffi::LUA_GCPSTEPSIZE, v);
}
match ffi::lua_gc(state, ffi::LUA_GCINC) {
ffi::LUA_GCINC => GcMode::Incremental(GcIncParams::default()),
ffi::LUA_GCGEN => GcMode::Generational(GcGenParams::default()),
_ => unreachable!(),
}
},
#[cfg(feature = "lua54")]
GcMode::Incremental(params) => unsafe {
let pause = params.pause.unwrap_or(0);
let step_mul = params.step_multiplier.unwrap_or(0);
let step_size = params.step_size.unwrap_or(0);
match ffi::lua_gc(state, ffi::LUA_GCINC, pause, step_mul, step_size) {
ffi::LUA_GCINC => GcMode::Incremental(GcIncParams::default()),
ffi::LUA_GCGEN => GcMode::Generational(GcGenParams::default()),
_ => unreachable!(),
}
},
#[cfg(any(feature = "lua53", feature = "lua52", feature = "lua51", feature = "luajit"))]
GcMode::Incremental(params) => unsafe {
if let Some(v) = params.pause {
ffi::lua_gc(state, ffi::LUA_GCSETPAUSE, v);
}
if let Some(v) = params.step_multiplier {
ffi::lua_gc(state, ffi::LUA_GCSETSTEPMUL, v);
}
GcMode::Incremental(GcIncParams::default())
},
#[cfg(feature = "luau")]
return ffi::lua_gc(state, ffi::LUA_GCSETGOAL, pause);
}
}
GcMode::Incremental(params) => unsafe {
if let Some(v) = params.goal {
ffi::lua_gc(state, ffi::LUA_GCSETGOAL, v);
}
if let Some(v) = params.step_multiplier {
ffi::lua_gc(state, ffi::LUA_GCSETSTEPMUL, v);
}
if let Some(v) = params.step_size {
ffi::lua_gc(state, ffi::LUA_GCSETSTEPSIZE, v);
}
GcMode::Incremental(GcIncParams::default())
},
/// Sets the `step multiplier` value of the collector.
///
/// Returns the previous value of the `step multiplier`. More information can be found in the
/// Lua [documentation].
///
/// [documentation]: https://www.lua.org/manual/5.4/manual.html#2.5
pub fn gc_set_step_multiplier(&self, step_multiplier: c_int) -> c_int {
let lua = self.lock();
unsafe {
#[cfg(feature = "lua55")]
return ffi::lua_gc(
lua.main_state(),
ffi::LUA_GCPARAM,
ffi::LUA_GCPSTEPMUL,
step_multiplier,
);
#[cfg(not(feature = "lua55"))]
return ffi::lua_gc(lua.main_state(), ffi::LUA_GCSETSTEPMUL, step_multiplier);
}
}
/// Changes the collector to incremental mode with the given parameters.
///
/// Returns the previous mode (always `GCMode::Incremental` in Lua < 5.4).
/// More information can be found in the Lua [documentation].
///
/// [documentation]: https://www.lua.org/manual/5.4/manual.html#2.5.1
pub fn gc_inc(&self, pause: c_int, step_multiplier: c_int, step_size: c_int) -> GCMode {
let lua = self.lock();
let state = lua.main_state();
#[cfg(any(
feature = "lua53",
feature = "lua52",
feature = "lua51",
feature = "luajit",
feature = "luau"
))]
unsafe {
if pause > 0 {
#[cfg(not(feature = "luau"))]
ffi::lua_gc(state, ffi::LUA_GCSETPAUSE, pause);
#[cfg(feature = "luau")]
ffi::lua_gc(state, ffi::LUA_GCSETGOAL, pause);
}
if step_multiplier > 0 {
ffi::lua_gc(state, ffi::LUA_GCSETSTEPMUL, step_multiplier);
}
#[cfg(feature = "luau")]
if step_size > 0 {
ffi::lua_gc(state, ffi::LUA_GCSETSTEPSIZE, step_size);
}
#[cfg(not(feature = "luau"))]
let _ = step_size; // Ignored
GCMode::Incremental
}
#[cfg(feature = "lua55")]
let prev_mode = unsafe {
ffi::lua_gc(state, ffi::LUA_GCPARAM, ffi::LUA_GCPPAUSE, pause);
ffi::lua_gc(state, ffi::LUA_GCPARAM, ffi::LUA_GCPSTEPMUL, step_multiplier);
ffi::lua_gc(state, ffi::LUA_GCPARAM, ffi::LUA_GCPSTEPSIZE, step_size);
ffi::lua_gc(state, ffi::LUA_GCINC)
};
#[cfg(feature = "lua54")]
let prev_mode = unsafe { ffi::lua_gc(state, ffi::LUA_GCINC, pause, step_multiplier, step_size) };
#[cfg(any(feature = "lua55", feature = "lua54"))]
match prev_mode {
ffi::LUA_GCINC => GCMode::Incremental,
ffi::LUA_GCGEN => GCMode::Generational,
_ => unreachable!(),
}
}
/// Changes the collector to generational mode with the given parameters.
///
/// Returns the previous mode. More information about the generational GC
/// can be found in the Lua 5.4 [documentation][lua_doc].
///
/// [lua_doc]: https://www.lua.org/manual/5.4/manual.html#2.5.2
#[cfg(any(feature = "lua55", feature = "lua54"))]
#[cfg_attr(docsrs, doc(cfg(any(feature = "lua55", feature = "lua54"))))]
pub fn gc_gen(&self, minor_multiplier: c_int, major_multiplier: c_int) -> GCMode {
let lua = self.lock();
let state = lua.main_state();
#[cfg(feature = "lua55")]
let prev_mode = unsafe {
ffi::lua_gc(state, ffi::LUA_GCPARAM, ffi::LUA_GCPMINORMUL, minor_multiplier);
ffi::lua_gc(state, ffi::LUA_GCPARAM, ffi::LUA_GCPMINORMAJOR, major_multiplier);
// TODO: LUA_GCPMAJORMINOR
ffi::lua_gc(state, ffi::LUA_GCGEN)
};
#[cfg(not(feature = "lua55"))]
let prev_mode = unsafe { ffi::lua_gc(state, ffi::LUA_GCGEN, minor_multiplier, major_multiplier) };
match prev_mode {
ffi::LUA_GCGEN => GCMode::Generational,
ffi::LUA_GCINC => GCMode::Incremental,
_ => unreachable!(),
GcMode::Generational(params) => unsafe {
if let Some(v) = params.minor_multiplier {
ffi::lua_gc(state, ffi::LUA_GCPARAM, ffi::LUA_GCPMINORMUL, v);
}
if let Some(v) = params.minor_to_major {
ffi::lua_gc(state, ffi::LUA_GCPARAM, ffi::LUA_GCPMINORMAJOR, v);
}
if let Some(v) = params.major_to_minor {
ffi::lua_gc(state, ffi::LUA_GCPARAM, ffi::LUA_GCPMAJORMINOR, v);
}
match ffi::lua_gc(state, ffi::LUA_GCGEN) {
ffi::LUA_GCGEN => GcMode::Generational(GcGenParams::default()),
ffi::LUA_GCINC => GcMode::Incremental(GcIncParams::default()),
_ => unreachable!(),
}
},
#[cfg(feature = "lua54")]
GcMode::Generational(params) => unsafe {
let minor = params.minor_multiplier.unwrap_or(0);
let minor_to_major = params.minor_to_major.unwrap_or(0);
match ffi::lua_gc(state, ffi::LUA_GCGEN, minor, minor_to_major) {
ffi::LUA_GCGEN => GcMode::Generational(GcGenParams::default()),
ffi::LUA_GCINC => GcMode::Incremental(GcIncParams::default()),
_ => unreachable!(),
}
},
}
}
@@ -1456,7 +1515,7 @@ impl Lua {
/// }
/// ```
///
/// [`AsyncThread`]: crate::AsyncThread
/// [`AsyncThread`]: crate::thread::AsyncThread
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
pub fn create_async_function<F, A, FR, R>(&self, func: F) -> Result<Function>
@@ -1492,7 +1551,7 @@ impl Lua {
#[inline]
pub fn create_userdata<T>(&self, data: T) -> Result<AnyUserData>
where
T: UserData + MaybeSend + 'static,
T: UserData + MaybeSend + MaybeSync + 'static,
{
unsafe { self.lock().make_userdata(UserDataStorage::new(data)) }
}
@@ -1503,7 +1562,7 @@ impl Lua {
#[inline]
pub fn create_ser_userdata<T>(&self, data: T) -> Result<AnyUserData>
where
T: UserData + Serialize + MaybeSend + 'static,
T: UserData + Serialize + MaybeSend + MaybeSync + 'static,
{
unsafe { self.lock().make_userdata(UserDataStorage::new_ser(data)) }
}
@@ -1518,7 +1577,7 @@ impl Lua {
#[inline]
pub fn create_any_userdata<T>(&self, data: T) -> Result<AnyUserData>
where
T: MaybeSend + 'static,
T: MaybeSend + MaybeSync + 'static,
{
unsafe { self.lock().make_any_userdata(UserDataStorage::new(data)) }
}
@@ -1531,7 +1590,7 @@ impl Lua {
#[inline]
pub fn create_ser_any_userdata<T>(&self, data: T) -> Result<AnyUserData>
where
T: Serialize + MaybeSend + 'static,
T: Serialize + MaybeSend + MaybeSync + 'static,
{
unsafe { (self.lock()).make_any_userdata(UserDataStorage::new_ser(data)) }
}
+38 -12
View File
@@ -16,7 +16,7 @@ use crate::stdlib::StdLib;
use crate::string::LuaString;
use crate::table::Table;
use crate::thread::Thread;
use crate::traits::IntoLua;
use crate::traits::{FromLua, IntoLua};
use crate::types::{
AppDataRef, AppDataRefMut, Callback, CallbackUpvalue, DestructedUserdata, Integer, LightUserData,
LuaType, MaybeSend, ReentrantMutex, RegistryKey, ValueRef, XRc,
@@ -50,7 +50,7 @@ use {
std::task::{Context, Poll, Waker},
};
/// An inner Lua struct which holds a raw Lua state.
/// An internal Lua struct which holds a raw Lua state.
#[doc(hidden)]
pub struct RawLua {
// The state is dynamic and depends on context
@@ -731,14 +731,27 @@ impl RawLua {
/// Pushes a value that implements `IntoLua` onto the Lua stack.
///
/// Uses up to 2 stack spaces to push a single value, does not call `checkstack`.
#[allow(clippy::missing_safety_doc)]
#[inline(always)]
pub unsafe fn push(&self, value: impl IntoLua) -> Result<()> {
value.push_into_stack(self)
}
/// Pops a value that implements [`FromLua`] from the top of the Lua stack.
///
/// Uses up to 1 stack space, does not call `checkstack`.
#[allow(clippy::missing_safety_doc)]
#[inline(always)]
pub unsafe fn pop<R: FromLua>(&self) -> Result<R> {
let v = R::from_stack(-1, self)?;
ffi::lua_pop(self.state(), 1);
Ok(v)
}
/// Pushes a `Value` (by reference) onto the Lua stack.
///
/// Uses 2 stack spaces, does not call `checkstack`.
/// Uses up to 2 stack spaces, does not call `checkstack`.
#[allow(clippy::missing_safety_doc)]
pub unsafe fn push_value(&self, value: &Value) -> Result<()> {
let state = self.state();
match value {
@@ -773,6 +786,7 @@ impl RawLua {
/// Pops a value from the Lua stack.
///
/// Uses up to 1 stack spaces, does not call `checkstack`.
#[allow(clippy::missing_safety_doc)]
#[inline]
pub unsafe fn pop_value(&self) -> Value {
let value = self.stack_value(-1, None);
@@ -803,15 +817,22 @@ impl RawLua {
#[cfg(any(feature = "lua52", feature = "lua51", feature = "luajit", feature = "luau"))]
ffi::LUA_TNUMBER => {
use crate::types::Number;
let n = ffi::lua_tonumber(state, idx);
match num_traits::cast(n) {
Some(i) if n.to_bits() == (i as Number).to_bits() => Value::Integer(i),
Some(i) if n.to_bits() == (i as crate::types::Number).to_bits() => Value::Integer(i),
_ => Value::Number(n),
}
}
#[cfg(feature = "luau")]
ffi::LUA_TINTEGER => {
let i = ffi::lua_tointeger64(state, idx, ptr::null_mut());
match num_traits::cast(i) {
Some(i) => Value::Integer(i),
_ => Value::Number(i as crate::types::Number),
}
}
#[cfg(feature = "luau")]
ffi::LUA_TVECTOR => {
let v = ffi::lua_tovector(state, idx);
@@ -949,7 +970,7 @@ impl RawLua {
// Check if userdata/metatable is already registered
let type_id = TypeId::of::<T>();
if let Some(&table_id) = (*self.extra.get()).registered_userdata_t.get(&type_id) {
return Ok(table_id as Integer);
return Ok(table_id);
}
// Create a new metatable from `UserData` definition
@@ -968,7 +989,7 @@ impl RawLua {
// Check if userdata/metatable is already registered
let type_id = TypeId::of::<T>();
if let Some(&table_id) = (*self.extra.get()).registered_userdata_t.get(&type_id) {
return Ok(table_id as Integer);
return Ok(table_id);
}
// Check if metatable creation is pending or create an empty metatable otherwise
@@ -983,7 +1004,7 @@ impl RawLua {
unsafe fn make_userdata_with_metatable<T>(
&self,
data: UserDataStorage<T>,
get_metatable_id: impl FnOnce() -> Result<Integer>,
get_metatable_id: impl FnOnce() -> Result<c_int>,
) -> Result<AnyUserData> {
let state = self.state();
let _sg = StackGuard::new(state);
@@ -993,7 +1014,7 @@ impl RawLua {
let mt_id = get_metatable_id()?;
let protect = !self.unlikely_memory_error();
push_userdata(state, data, protect)?;
ffi::lua_rawgeti(state, ffi::LUA_REGISTRYINDEX, mt_id);
ffi::lua_rawgeti(state, ffi::LUA_REGISTRYINDEX, mt_id as _);
ffi::lua_setmetatable(state, -2);
// Set empty environment for Lua 5.1
@@ -1011,7 +1032,7 @@ impl RawLua {
Ok(AnyUserData(self.pop_ref()))
}
pub(crate) unsafe fn create_userdata_metatable(&self, registry: RawUserDataRegistry) -> Result<Integer> {
pub(crate) unsafe fn create_userdata_metatable(&self, registry: RawUserDataRegistry) -> Result<c_int> {
let state = self.state();
let type_id = registry.type_id;
@@ -1027,7 +1048,7 @@ impl RawLua {
}
self.register_userdata_metatable(mt_ptr, type_id);
Ok(id as Integer)
Ok(id)
}
pub(crate) unsafe fn push_userdata_metatable(&self, mut registry: RawUserDataRegistry) -> Result<()> {
@@ -1584,6 +1605,11 @@ unsafe fn load_std_libs(state: *mut ffi::lua_State, libs: StdLib) -> Result<()>
requiref(state, ffi::LUA_VECLIBNAME, ffi::luaopen_vector, 1)?;
}
#[cfg(feature = "luau")]
if libs.contains(StdLib::INTEGER) {
requiref(state, ffi::LUA_INTLIBNAME, ffi::luaopen_integer, 1)?;
}
if libs.contains(StdLib::MATH) {
requiref(state, ffi::LUA_MATHLIBNAME, ffi::luaopen_math, 1)?;
}
+6 -1
View File
@@ -73,10 +73,15 @@ impl StdLib {
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
pub const VECTOR: StdLib = StdLib(1 << 10);
/// [`integer`](https://luau.org/library#integer-library) library
#[cfg(any(feature = "luau", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
pub const INTEGER: StdLib = StdLib(1 << 11);
/// [`jit`](http://luajit.org/ext_jit.html) library
#[cfg(any(feature = "luajit", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luajit")))]
pub const JIT: StdLib = StdLib(1 << 11);
pub const JIT: StdLib = StdLib(1 << 12);
/// (**unsafe**) [`ffi`](http://luajit.org/ext_ffi.html) library
#[cfg(any(feature = "luajit", doc))]
+51 -41
View File
@@ -1,8 +1,12 @@
use std::borrow::{Borrow, Cow};
//! Lua string handling.
//!
//! This module provides types for working with Lua strings from Rust.
use std::borrow::Borrow;
use std::hash::{Hash, Hasher};
use std::ops::Deref;
use std::os::raw::{c_int, c_void};
use std::{cmp, fmt, slice, str};
use std::{cmp, fmt, mem, slice, str};
use crate::error::{Error, Result};
use crate::state::Lua;
@@ -25,6 +29,9 @@ pub struct LuaString(pub(crate) ValueRef);
impl LuaString {
/// Get a [`BorrowedStr`] if the Lua string is valid UTF-8.
///
/// The returned `BorrowedStr` holds a strong reference to the Lua state to guarantee the
/// validity of the underlying data.
///
/// # Examples
///
/// ```
@@ -42,7 +49,7 @@ impl LuaString {
/// # }
/// ```
#[inline]
pub fn to_str(&self) -> Result<BorrowedStr<'_>> {
pub fn to_str(&self) -> Result<BorrowedStr> {
BorrowedStr::try_from(self)
}
@@ -85,8 +92,9 @@ impl LuaString {
/// Get the bytes that make up this string.
///
/// The returned slice will not contain the terminating null byte, but will contain any null
/// bytes embedded into the Lua string.
/// The returned `BorrowedStr` holds a strong reference to the Lua state to guarantee the
/// validity of the underlying data. The data will not contain the terminating null byte, but
/// will contain any null bytes embedded into the Lua string.
///
/// # Examples
///
@@ -101,16 +109,16 @@ impl LuaString {
/// # }
/// ```
#[inline]
pub fn as_bytes(&self) -> BorrowedBytes<'_> {
pub fn as_bytes(&self) -> BorrowedBytes {
BorrowedBytes::from(self)
}
/// Get the bytes that make up this string, including the trailing null byte.
pub fn as_bytes_with_nul(&self) -> BorrowedBytes<'_> {
let BorrowedBytes { buf, borrow, _lua } = BorrowedBytes::from(self);
pub fn as_bytes_with_nul(&self) -> BorrowedBytes {
let BorrowedBytes { buf, vref, _lua } = BorrowedBytes::from(self);
// Include the trailing null byte (it's always present but excluded by default)
let buf = unsafe { slice::from_raw_parts((*buf).as_ptr(), (*buf).len() + 1) };
BorrowedBytes { buf, borrow, _lua }
BorrowedBytes { buf, vref, _lua }
}
// Does not return the terminating null byte
@@ -233,14 +241,14 @@ impl fmt::Display for Display<'_> {
}
/// A borrowed string (`&str`) that holds a strong reference to the Lua state.
pub struct BorrowedStr<'a> {
pub struct BorrowedStr {
// `buf` points to a readonly memory managed by Lua
pub(crate) buf: &'a str,
pub(crate) borrow: Cow<'a, LuaString>,
pub(crate) buf: &'static str,
pub(crate) vref: ValueRef,
pub(crate) _lua: Lua,
}
impl Deref for BorrowedStr<'_> {
impl Deref for BorrowedStr {
type Target = str;
#[inline(always)]
@@ -249,33 +257,33 @@ impl Deref for BorrowedStr<'_> {
}
}
impl Borrow<str> for BorrowedStr<'_> {
impl Borrow<str> for BorrowedStr {
#[inline(always)]
fn borrow(&self) -> &str {
self.buf
}
}
impl AsRef<str> for BorrowedStr<'_> {
impl AsRef<str> for BorrowedStr {
#[inline(always)]
fn as_ref(&self) -> &str {
self.buf
}
}
impl fmt::Display for BorrowedStr<'_> {
impl fmt::Display for BorrowedStr {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.buf.fmt(f)
}
}
impl fmt::Debug for BorrowedStr<'_> {
impl fmt::Debug for BorrowedStr {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.buf.fmt(f)
}
}
impl<T> PartialEq<T> for BorrowedStr<'_>
impl<T> PartialEq<T> for BorrowedStr
where
T: AsRef<str>,
{
@@ -284,9 +292,9 @@ where
}
}
impl Eq for BorrowedStr<'_> {}
impl Eq for BorrowedStr {}
impl<T> PartialOrd<T> for BorrowedStr<'_>
impl<T> PartialOrd<T> for BorrowedStr
where
T: AsRef<str>,
{
@@ -295,33 +303,33 @@ where
}
}
impl Ord for BorrowedStr<'_> {
impl Ord for BorrowedStr {
fn cmp(&self, other: &Self) -> cmp::Ordering {
self.buf.cmp(other.buf)
}
}
impl<'a> TryFrom<&'a LuaString> for BorrowedStr<'a> {
impl TryFrom<&LuaString> for BorrowedStr {
type Error = Error;
#[inline]
fn try_from(value: &'a LuaString) -> Result<Self> {
let BorrowedBytes { buf, borrow, _lua } = BorrowedBytes::from(value);
fn try_from(value: &LuaString) -> Result<Self> {
let BorrowedBytes { buf, vref, _lua } = BorrowedBytes::from(value);
let buf =
str::from_utf8(buf).map_err(|e| Error::from_lua_conversion("string", "&str", e.to_string()))?;
Ok(Self { buf, borrow, _lua })
Ok(Self { buf, vref, _lua })
}
}
/// A borrowed byte slice (`&[u8]`) that holds a strong reference to the Lua state.
pub struct BorrowedBytes<'a> {
pub struct BorrowedBytes {
// `buf` points to a readonly memory managed by Lua
pub(crate) buf: &'a [u8],
pub(crate) borrow: Cow<'a, LuaString>,
pub(crate) buf: &'static [u8],
pub(crate) vref: ValueRef,
pub(crate) _lua: Lua,
}
impl Deref for BorrowedBytes<'_> {
impl Deref for BorrowedBytes {
type Target = [u8];
#[inline(always)]
@@ -330,27 +338,27 @@ impl Deref for BorrowedBytes<'_> {
}
}
impl Borrow<[u8]> for BorrowedBytes<'_> {
impl Borrow<[u8]> for BorrowedBytes {
#[inline(always)]
fn borrow(&self) -> &[u8] {
self.buf
}
}
impl AsRef<[u8]> for BorrowedBytes<'_> {
impl AsRef<[u8]> for BorrowedBytes {
#[inline(always)]
fn as_ref(&self) -> &[u8] {
self.buf
}
}
impl fmt::Debug for BorrowedBytes<'_> {
impl fmt::Debug for BorrowedBytes {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.buf.fmt(f)
}
}
impl<T> PartialEq<T> for BorrowedBytes<'_>
impl<T> PartialEq<T> for BorrowedBytes
where
T: AsRef<[u8]>,
{
@@ -359,9 +367,9 @@ where
}
}
impl Eq for BorrowedBytes<'_> {}
impl Eq for BorrowedBytes {}
impl<T> PartialOrd<T> for BorrowedBytes<'_>
impl<T> PartialOrd<T> for BorrowedBytes
where
T: AsRef<[u8]>,
{
@@ -370,13 +378,13 @@ where
}
}
impl Ord for BorrowedBytes<'_> {
impl Ord for BorrowedBytes {
fn cmp(&self, other: &Self) -> cmp::Ordering {
self.buf.cmp(other.buf)
}
}
impl<'a> IntoIterator for &'a BorrowedBytes<'_> {
impl<'a> IntoIterator for &'a BorrowedBytes {
type Item = &'a u8;
type IntoIter = slice::Iter<'a, u8>;
@@ -385,12 +393,14 @@ impl<'a> IntoIterator for &'a BorrowedBytes<'_> {
}
}
impl<'a> From<&'a LuaString> for BorrowedBytes<'a> {
impl From<&LuaString> for BorrowedBytes {
#[inline]
fn from(value: &'a LuaString) -> Self {
fn from(value: &LuaString) -> Self {
let (buf, _lua) = unsafe { value.to_slice() };
let borrow = Cow::Borrowed(value);
Self { buf, borrow, _lua }
let vref = value.0.clone();
// SAFETY: The `buf` is valid for the lifetime of the Lua state and occupied slot index
let buf = unsafe { mem::transmute::<&[u8], &'static [u8]>(buf) };
Self { buf, vref, _lua }
}
}
+2 -11
View File
@@ -3,12 +3,6 @@
//! Tables are Lua's primary data structure, used for arrays, dictionaries, objects, modules,
//! and more. This module provides types for creating and manipulating Lua tables from Rust.
//!
//! # Main Types
//!
//! - [`Table`] - A handle to a Lua table.
//! - [`TablePairs`] - An iterator over key-value pairs in a table.
//! - [`TableSequence`] - An iterator over the array (sequence) portion of a table.
//!
//! # Basic Operations
//!
//! Tables support key-value access similar to Rust's `HashMap`:
@@ -784,10 +778,8 @@ impl Table {
ffi::lua_pushnil(state);
while ffi::lua_next(state, -2) != 0 {
let k = K::from_stack(-2, &lua)?;
let v = V::from_stack(-1, &lua)?;
let v = lua.pop::<V>()?;
f(k, v)?;
// Keep key for next iteration
ffi::lua_pop(state, 1);
}
}
Ok(())
@@ -860,8 +852,7 @@ impl Table {
if len.is_none() && t == ffi::LUA_TNIL {
break;
}
f(V::from_stack(-1, &lua)?)?;
ffi::lua_pop(state, 1);
f(lua.pop::<V>()?)?;
}
}
Ok(())
+66 -9
View File
@@ -1,3 +1,40 @@
//! Lua thread (coroutine) handling.
//!
//! This module provides types for creating and working with Lua coroutines from Rust.
//! Coroutines allow cooperative multitasking within a single Lua state by suspending and
//! resuming execution at well-defined yield points.
//!
//! # Basic Usage
//!
//! Threads are created via [`Lua::create_thread`] and driven by calling [`Thread::resume`]:
//!
//! ```rust
//! # use mlua::{Lua, Result, Thread};
//! # fn main() -> Result<()> {
//! let lua = Lua::new();
//! let thread: Thread = lua.load(r#"
//! coroutine.create(function(a, b)
//! coroutine.yield(a + b)
//! return a * b
//! end)
//! "#).eval()?;
//!
//! assert_eq!(thread.resume::<i32>((3, 4))?, 7);
//! assert_eq!(thread.resume::<i32>(())?, 12);
//! # Ok(())
//! # }
//! ```
//!
//! # Async Support
//!
//! When the `async` feature is enabled, a [`Thread`] can be converted into an [`AsyncThread`]
//! via [`Thread::into_async`], which implements both [`Future`] and [`Stream`].
//! This integrates Lua coroutines naturally with Rust async runtimes such as Tokio.
//!
//! [`Lua::create_thread`]: crate::Lua::create_thread
//! [`Future`]: std::future::Future
//! [`Stream`]: futures_util::stream::Stream
use std::fmt;
use std::os::raw::{c_int, c_void};
@@ -69,7 +106,7 @@ impl ThreadStatusInner {
}
/// Handle to an internal Lua thread (coroutine).
#[derive(Clone)]
#[derive(Clone, PartialEq)]
pub struct Thread(pub(crate) ValueRef, pub(crate) *mut ffi::lua_State);
#[cfg(feature = "send")]
@@ -92,7 +129,6 @@ pub struct AsyncThread<R> {
impl Thread {
/// Returns reference to the Lua state that this thread is associated with.
#[doc(hidden)]
#[inline(always)]
pub fn state(&self) -> *mut ffi::lua_State {
self.1
@@ -259,6 +295,31 @@ impl Thread {
}
}
/// Returns `true` if this thread is resumable (meaning it can be resumed by calling
/// [`Thread::resume`]).
#[inline(always)]
pub fn is_resumable(&self) -> bool {
self.status() == ThreadStatus::Resumable
}
/// Returns `true` if this thread is currently running.
#[inline(always)]
pub fn is_running(&self) -> bool {
self.status() == ThreadStatus::Running
}
/// Returns `true` if this thread has finished executing.
#[inline(always)]
pub fn is_finished(&self) -> bool {
self.status() == ThreadStatus::Finished
}
/// Returns `true` if this thread has raised a Lua error during execution.
#[inline(always)]
pub fn is_error(&self) -> bool {
self.status() == ThreadStatus::Error
}
/// Sets a hook function that will periodically be called as Lua code executes.
///
/// This function is similar or [`Lua::set_hook`] except that it sets for the thread.
@@ -295,7 +356,7 @@ impl Thread {
/// Resets a thread
///
/// In [Lua 5.4]: cleans its call stack and closes all pending to-be-closed variables.
/// Returns a error in case of either the original error that stopped the thread or errors
/// Returns an error in case of either the original error that stopped the thread or errors
/// in closing methods.
///
/// In Luau: resets to the initial state of a newly created Lua thread.
@@ -449,6 +510,8 @@ impl Thread {
/// Please note that Luau links environment table with chunk when loading it into Lua state.
/// Therefore you need to load chunks into a thread to link with the thread environment.
///
/// [`Lua::sandbox`]: crate::Lua::sandbox
///
/// # Examples
///
/// ```
@@ -502,12 +565,6 @@ impl fmt::Debug for Thread {
}
}
impl PartialEq for Thread {
fn eq(&self, other: &Self) -> bool {
self.0 == other.0
}
}
impl LuaType for Thread {
const TYPE_ID: c_int = ffi::LUA_TTHREAD;
}
+6 -93
View File
@@ -1,3 +1,8 @@
//! Core conversion and extension traits.
//!
//! This module provides the fundamental traits for converting values between Rust and Lua,
//! and for defining native Lua callable functions.
use std::os::raw::c_int;
use std::sync::Arc;
@@ -5,12 +10,11 @@ use crate::error::{Error, Result};
use crate::multi::MultiValue;
use crate::private::Sealed;
use crate::state::{Lua, RawLua, WeakLua};
use crate::types::MaybeSend;
use crate::util::{check_stack, parse_lookup_path, short_type_name};
use crate::value::Value;
#[cfg(feature = "async")]
use {crate::function::AsyncCallFuture, std::future::Future};
use crate::function::AsyncCallFuture;
/// Trait for types convertible to [`Value`].
pub trait IntoLua: Sized {
@@ -245,97 +249,6 @@ pub trait ObjectLike: Sealed {
fn weak_lua(&self) -> &WeakLua;
}
/// A trait for types that can be used as Lua functions.
pub trait LuaNativeFn<A: FromLuaMulti> {
type Output: IntoLuaMulti;
fn call(&self, args: A) -> Self::Output;
}
/// A trait for types with mutable state that can be used as Lua functions.
pub trait LuaNativeFnMut<A: FromLuaMulti> {
type Output: IntoLuaMulti;
fn call(&mut self, args: A) -> Self::Output;
}
/// A trait for types that returns a future and can be used as Lua functions.
#[cfg(feature = "async")]
pub trait LuaNativeAsyncFn<A: FromLuaMulti> {
type Output: IntoLuaMulti;
fn call(&self, args: A) -> impl Future<Output = Self::Output> + MaybeSend + 'static;
}
macro_rules! impl_lua_native_fn {
($($A:ident),*) => {
impl<FN, $($A,)* R> LuaNativeFn<($($A,)*)> for FN
where
FN: Fn($($A,)*) -> R + MaybeSend + 'static,
($($A,)*): FromLuaMulti,
R: IntoLuaMulti,
{
type Output = R;
#[allow(non_snake_case)]
fn call(&self, args: ($($A,)*)) -> Self::Output {
let ($($A,)*) = args;
self($($A,)*)
}
}
impl<FN, $($A,)* R> LuaNativeFnMut<($($A,)*)> for FN
where
FN: FnMut($($A,)*) -> R + MaybeSend + 'static,
($($A,)*): FromLuaMulti,
R: IntoLuaMulti,
{
type Output = R;
#[allow(non_snake_case)]
fn call(&mut self, args: ($($A,)*)) -> Self::Output {
let ($($A,)*) = args;
self($($A,)*)
}
}
#[cfg(feature = "async")]
impl<FN, $($A,)* Fut, R> LuaNativeAsyncFn<($($A,)*)> for FN
where
FN: Fn($($A,)*) -> Fut + MaybeSend + 'static,
($($A,)*): FromLuaMulti,
Fut: Future<Output = R> + MaybeSend + 'static,
R: IntoLuaMulti,
{
type Output = R;
#[allow(non_snake_case)]
fn call(&self, args: ($($A,)*)) -> impl Future<Output = Self::Output> + MaybeSend + 'static {
let ($($A,)*) = args;
self($($A,)*)
}
}
};
}
impl_lua_native_fn!();
impl_lua_native_fn!(A);
impl_lua_native_fn!(A, B);
impl_lua_native_fn!(A, B, C);
impl_lua_native_fn!(A, B, C, D);
impl_lua_native_fn!(A, B, C, D, E);
impl_lua_native_fn!(A, B, C, D, E, F);
impl_lua_native_fn!(A, B, C, D, E, F, G);
impl_lua_native_fn!(A, B, C, D, E, F, G, H);
impl_lua_native_fn!(A, B, C, D, E, F, G, H, I);
impl_lua_native_fn!(A, B, C, D, E, F, G, H, I, J);
impl_lua_native_fn!(A, B, C, D, E, F, G, H, I, J, K);
impl_lua_native_fn!(A, B, C, D, E, F, G, H, I, J, K, L);
impl_lua_native_fn!(A, B, C, D, E, F, G, H, I, J, K, L, M);
impl_lua_native_fn!(A, B, C, D, E, F, G, H, I, J, K, L, M, N);
impl_lua_native_fn!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O);
impl_lua_native_fn!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P);
pub(crate) trait ShortTypeName {
#[inline(always)]
fn type_name() -> String {
+12
View File
@@ -128,6 +128,18 @@ pub trait MaybeSend {}
#[cfg(not(feature = "send"))]
impl<T> MaybeSend for T {}
/// A trait that adds `Sync` requirement if `send` feature is enabled.
#[cfg(feature = "send")]
pub trait MaybeSync: Sync {}
#[cfg(feature = "send")]
impl<T: Sync> MaybeSync for T {}
/// A trait that adds `Sync` requirement if `send` feature is enabled.
#[cfg(not(feature = "send"))]
pub trait MaybeSync {}
#[cfg(not(feature = "send"))]
impl<T> MaybeSync for T {}
pub(crate) struct DestructedUserdata;
pub(crate) trait LuaType {
+62 -11
View File
@@ -1,16 +1,21 @@
//! Lua userdata handling.
//!
//! This module provides types for creating and working with Lua userdata from Rust.
use std::any::TypeId;
use std::ffi::CStr;
use std::fmt;
use std::hash::Hash;
use std::os::raw::{c_char, c_void};
use crate::Either;
use crate::error::{Error, Result};
use crate::function::Function;
use crate::state::Lua;
use crate::string::LuaString;
use crate::table::{Table, TablePairs};
use crate::traits::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti};
use crate::types::{MaybeSend, ValueRef};
use crate::types::{MaybeSend, MaybeSync, ValueRef};
use crate::util::{StackGuard, check_stack, get_userdata, push_string, short_type_name, take_userdata};
use crate::value::Value;
@@ -25,7 +30,7 @@ use {
// Re-export for convenience
pub(crate) use cell::UserDataStorage;
pub use r#ref::{UserDataRef, UserDataRefMut};
pub use r#ref::{UserDataOwned, UserDataRef, UserDataRefMut};
pub use registry::UserDataRegistry;
pub(crate) use registry::{RawUserDataRegistry, UserDataProxy};
pub(crate) use util::{
@@ -123,6 +128,11 @@ pub enum MetaMethod {
///
/// This is not an operator, but will be called by methods such as `tostring` and `print`.
ToString,
/// The `__todebugstring` metamethod for debug purposes.
///
/// This is an mlua-specific metamethod that can be used to provide debug representation for
/// userdata.
ToDebugString,
/// The `__pairs` metamethod.
///
/// This is not an operator, but it will be called by the built-in `pairs` function.
@@ -232,6 +242,7 @@ impl MetaMethod {
MetaMethod::NewIndex => "__newindex",
MetaMethod::Call => "__call",
MetaMethod::ToString => "__tostring",
MetaMethod::ToDebugString => "__todebugstring",
#[cfg(any(
feature = "lua55",
@@ -318,7 +329,6 @@ pub trait UserDataMethods<T> {
///
/// The method can be called only once per userdata instance, subsequent calls will result in a
/// [`Error::UserDataDestructed`] error.
#[doc(hidden)]
fn add_method_once<M, A, R>(&mut self, name: impl Into<String>, method: M)
where
T: 'static,
@@ -373,7 +383,6 @@ pub trait UserDataMethods<T> {
/// [`Error::UserDataDestructed`] error.
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
#[doc(hidden)]
fn add_async_method_once<M, A, MR, R>(&mut self, name: impl Into<String>, method: M)
where
T: 'static,
@@ -705,7 +714,7 @@ pub trait UserData: Sized {
///
/// [`is`]: crate::AnyUserData::is
/// [`borrow`]: crate::AnyUserData::borrow
#[derive(Clone, Debug, PartialEq)]
#[derive(Clone, PartialEq)]
pub struct AnyUserData(pub(crate) ValueRef);
impl AnyUserData {
@@ -1020,8 +1029,8 @@ impl AnyUserData {
/// Returns a type name of this userdata (from a metatable field).
///
/// If no type name is set, returns `None`.
pub fn type_name(&self) -> Result<Option<String>> {
/// If no type name is set, returns `userdata`.
pub fn type_name(&self) -> Result<LuaString> {
let lua = self.0.lua.lock();
let state = lua.state();
unsafe {
@@ -1038,8 +1047,8 @@ impl AnyUserData {
ffi::luaL_getmetafield(state, -1, MetaMethod::Type.as_cstr().as_ptr())
};
match name_type {
ffi::LUA_TSTRING => Ok(Some(LuaString(lua.pop_ref()).to_str()?.to_owned())),
_ => Ok(None),
ffi::LUA_TSTRING => Ok(LuaString(lua.pop_ref())),
_ => lua.create_string(b"userdata"),
}
}
}
@@ -1075,6 +1084,48 @@ impl AnyUserData {
};
is_serializable().unwrap_or(false)
}
unsafe fn invoke_tostring_dbg(&self) -> Result<Option<String>> {
let lua = self.0.lua.lock();
let state = lua.state();
let _guard = StackGuard::new(state);
check_stack(state, 3)?;
lua.push_ref(&self.0);
protect_lua!(state, 1, 1, fn(state) {
// Try `__todebugstring` metamethod first, then `__tostring`
#[allow(clippy::collapsible_if)]
if ffi::luaL_callmeta(state, -1, cstr!("__todebugstring")) == 0 {
if ffi::luaL_callmeta(state, -1, cstr!("__tostring")) == 0 {
ffi::lua_pushnil(state);
}
}
})?;
Ok(lua.pop_value().as_string().map(|s| s.to_string_lossy()))
}
pub(crate) fn fmt_pretty(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
// Try converting to a (debug) string first, with fallback to `__name/__type`
match unsafe { self.invoke_tostring_dbg() } {
Ok(Some(s)) => write!(fmt, "{s}"),
_ => {
let name = self.type_name().ok();
let name = (name.as_ref())
.map(|s| Either::Left(s.display()))
.unwrap_or(Either::Right("userdata"));
write!(fmt, "{name}: {:?}", self.to_pointer())
}
}
}
}
impl fmt::Debug for AnyUserData {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
if fmt.alternate() {
return self.fmt_pretty(fmt);
}
fmt.debug_tuple("AnyUserData").field(&self.0).finish()
}
}
/// Handle to a [`AnyUserData`] metatable.
@@ -1171,7 +1222,7 @@ impl AnyUserData {
/// Wraps any Rust type, returning an opaque type that implements [`IntoLua`] trait.
///
/// This function uses [`Lua::create_any_userdata`] under the hood.
pub fn wrap<T: MaybeSend + 'static>(data: T) -> impl IntoLua {
pub fn wrap<T: MaybeSend + MaybeSync + 'static>(data: T) -> impl IntoLua {
WrappedUserdata(move |lua| lua.create_any_userdata(data))
}
@@ -1181,7 +1232,7 @@ impl AnyUserData {
/// This function uses [`Lua::create_ser_any_userdata`] under the hood.
#[cfg(feature = "serde")]
#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
pub fn wrap_ser<T: Serialize + MaybeSend + 'static>(data: T) -> impl IntoLua {
pub fn wrap_ser<T: Serialize + MaybeSend + MaybeSync + 'static>(data: T) -> impl IntoLua {
WrappedUserdata(move |lua| lua.create_ser_any_userdata(data))
}
}
+29 -61
View File
@@ -1,4 +1,4 @@
use std::cell::{RefCell, UnsafeCell};
use std::cell::RefCell;
#[cfg(feature = "serde")]
use serde::ser::{Serialize, Serializer};
@@ -6,14 +6,14 @@ use serde::ser::{Serialize, Serializer};
use crate::error::{Error, Result};
use crate::types::XRc;
use super::lock::{RawLock, UserDataLock};
use super::lock::{RawLock, RwLock, UserDataLock};
use super::r#ref::{UserDataRef, UserDataRefMut};
#[cfg(all(feature = "serde", not(feature = "send")))]
type DynSerialize = dyn erased_serde::Serialize;
#[cfg(all(feature = "serde", feature = "send"))]
type DynSerialize = dyn erased_serde::Serialize + Send;
type DynSerialize = dyn erased_serde::Serialize + Send + Sync;
pub(crate) enum UserDataStorage<T> {
Owned(UserDataVariant<T>),
@@ -23,9 +23,9 @@ pub(crate) enum UserDataStorage<T> {
// A enum for storing userdata values.
// It's stored inside a Lua VM and protected by the outer `ReentrantMutex`.
pub(crate) enum UserDataVariant<T> {
Default(XRc<UserDataCell<T>>),
Default(XRc<RwLock<T>>),
#[cfg(feature = "serde")]
Serializable(XRc<UserDataCell<Box<DynSerialize>>>, bool), // bool is `is_sync`
Serializable(XRc<RwLock<Box<DynSerialize>>>),
}
impl<T> Clone for UserDataVariant<T> {
@@ -34,7 +34,7 @@ impl<T> Clone for UserDataVariant<T> {
match self {
Self::Default(inner) => Self::Default(XRc::clone(inner)),
#[cfg(feature = "serde")]
Self::Serializable(inner, is_sync) => Self::Serializable(XRc::clone(inner), *is_sync),
Self::Serializable(inner) => Self::Serializable(XRc::clone(inner)),
}
}
}
@@ -42,10 +42,12 @@ impl<T> Clone for UserDataVariant<T> {
impl<T> UserDataVariant<T> {
#[inline(always)]
pub(super) fn try_borrow_scoped<R>(&self, f: impl FnOnce(&T) -> R) -> Result<R> {
// We don't need to check for `T: Sync` because when this method is used (internally),
// Lua mutex is already locked.
// If non-`Sync` userdata is already borrowed by another thread (via `UserDataRef`), it will be
// exclusively locked.
// Shared (read) lock is always correct for in-place borrows:
// - this method is called internally while the Lua mutex is held, ensuring exclusive Lua-level
// access per call frame
// - with `send` feature, all owned userdata satisfies `T: Sync`, so simultaneous shared references
// from multiple threads are sound
// - without `send` feature, single-threaded execution makes shared lock safe for any `T`
let _guard = (self.raw_lock().try_lock_shared_guarded()).map_err(|_| Error::UserDataBorrowError)?;
Ok(f(unsafe { &*self.as_ptr() }))
}
@@ -78,10 +80,12 @@ impl<T> UserDataVariant<T> {
return Err(Error::UserDataBorrowMutError);
}
Ok(match self {
Self::Default(inner) => XRc::into_inner(inner).unwrap().value.into_inner(),
Self::Default(inner) => XRc::into_inner(inner).unwrap().into_inner(),
#[cfg(feature = "serde")]
Self::Serializable(inner, _) => unsafe {
let raw = Box::into_raw(XRc::into_inner(inner).unwrap().value.into_inner());
Self::Serializable(inner) => unsafe {
// The serde variant erases `T` to `Box<DynSerialize>`, so we
// must cast the raw pointer back to recover the concrete type.
let raw = Box::into_raw(XRc::into_inner(inner).unwrap().into_inner());
*Box::from_raw(raw as *mut T)
},
})
@@ -92,25 +96,25 @@ impl<T> UserDataVariant<T> {
match self {
Self::Default(inner) => XRc::strong_count(inner),
#[cfg(feature = "serde")]
Self::Serializable(inner, _) => XRc::strong_count(inner),
Self::Serializable(inner) => XRc::strong_count(inner),
}
}
#[inline(always)]
pub(super) fn raw_lock(&self) -> &RawLock {
match self {
Self::Default(inner) => &inner.raw_lock,
Self::Default(inner) => unsafe { inner.raw() },
#[cfg(feature = "serde")]
Self::Serializable(inner, _) => &inner.raw_lock,
Self::Serializable(inner) => unsafe { inner.raw() },
}
}
#[inline(always)]
pub(super) fn as_ptr(&self) -> *mut T {
match self {
Self::Default(inner) => inner.value.get(),
Self::Default(inner) => inner.data_ptr(),
#[cfg(feature = "serde")]
Self::Serializable(inner, _) => unsafe { &mut **(inner.value.get() as *mut Box<T>) },
Self::Serializable(inner) => unsafe { (&mut **inner.data_ptr()) as *mut DynSerialize as *mut T },
}
}
}
@@ -119,51 +123,16 @@ impl<T> UserDataVariant<T> {
impl Serialize for UserDataStorage<()> {
fn serialize<S: Serializer>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error> {
match self {
Self::Owned(variant @ UserDataVariant::Serializable(inner, is_sync)) => unsafe {
#[cfg(feature = "send")]
if *is_sync {
let _guard = (variant.raw_lock().try_lock_shared_guarded())
.map_err(|_| serde::ser::Error::custom(Error::UserDataBorrowError))?;
(*inner.value.get()).serialize(serializer)
} else {
let _guard = (variant.raw_lock().try_lock_exclusive_guarded())
.map_err(|_| serde::ser::Error::custom(Error::UserDataBorrowError))?;
(*inner.value.get()).serialize(serializer)
}
#[cfg(not(feature = "send"))]
{
let _ = is_sync;
let _guard = (variant.raw_lock().try_lock_shared_guarded())
.map_err(|_| serde::ser::Error::custom(Error::UserDataBorrowError))?;
(*inner.value.get()).serialize(serializer)
}
Self::Owned(variant @ UserDataVariant::Serializable(inner)) => unsafe {
let _guard = (variant.raw_lock().try_lock_shared_guarded())
.map_err(|_| serde::ser::Error::custom(Error::UserDataBorrowError))?;
(*inner.data_ptr()).serialize(serializer)
},
_ => Err(serde::ser::Error::custom("cannot serialize <userdata>")),
}
}
}
/// A type that provides interior mutability for a userdata value (thread-safe).
pub(crate) struct UserDataCell<T> {
raw_lock: RawLock,
value: UnsafeCell<T>,
}
#[cfg(feature = "send")]
unsafe impl<T: Send> Send for UserDataCell<T> {}
#[cfg(feature = "send")]
unsafe impl<T: Send> Sync for UserDataCell<T> {}
impl<T> UserDataCell<T> {
#[inline(always)]
fn new(value: T) -> Self {
UserDataCell {
raw_lock: RawLock::INIT,
value: UnsafeCell::new(value),
}
}
}
pub(crate) enum ScopedUserDataVariant<T> {
Ref(*const T),
RefMut(RefCell<*mut T>),
@@ -184,7 +153,7 @@ impl<T> Drop for ScopedUserDataVariant<T> {
impl<T: 'static> UserDataStorage<T> {
#[inline(always)]
pub(crate) fn new(data: T) -> Self {
Self::Owned(UserDataVariant::Default(XRc::new(UserDataCell::new(data))))
Self::Owned(UserDataVariant::Default(XRc::new(RwLock::new(data))))
}
#[inline(always)]
@@ -201,11 +170,10 @@ impl<T: 'static> UserDataStorage<T> {
#[inline(always)]
pub(crate) fn new_ser(data: T) -> Self
where
T: Serialize + crate::types::MaybeSend,
T: Serialize + crate::types::MaybeSend + crate::types::MaybeSync,
{
let data = Box::new(data) as Box<DynSerialize>;
let is_sync = super::util::is_sync::<T>();
let variant = UserDataVariant::Serializable(XRc::new(UserDataCell::new(data)), is_sync);
let variant = UserDataVariant::Serializable(XRc::new(RwLock::new(data)));
Self::Owned(variant)
}
+44 -19
View File
@@ -1,6 +1,4 @@
pub(crate) trait UserDataLock {
const INIT: Self;
fn is_locked(&self) -> bool;
fn try_lock_shared(&self) -> bool;
fn try_lock_exclusive(&self) -> bool;
@@ -48,12 +46,12 @@ impl<L: UserDataLock + ?Sized> Drop for LockGuard<'_, L> {
}
}
pub(crate) use lock_impl::RawLock;
pub(crate) use lock_impl::{RawLock, RwLock};
#[cfg(not(feature = "send"))]
#[cfg(not(tarpaulin_include))]
mod lock_impl {
use std::cell::Cell;
use std::cell::{Cell, UnsafeCell};
// Positive values represent the number of read references.
// Negative values represent the number of write references (only one allowed).
@@ -62,9 +60,6 @@ mod lock_impl {
const UNUSED: isize = 0;
impl super::UserDataLock for RawLock {
#[allow(clippy::declare_interior_mutable_const)]
const INIT: Self = Cell::new(UNUSED);
#[inline(always)]
fn is_locked(&self) -> bool {
self.get() != UNUSED
@@ -72,7 +67,7 @@ mod lock_impl {
#[inline(always)]
fn try_lock_shared(&self) -> bool {
let flag = self.get().wrapping_add(1);
let flag = self.get().checked_add(1).expect("userdata lock count overflow");
if flag <= UNUSED {
return false;
}
@@ -104,41 +99,71 @@ mod lock_impl {
self.set(flag + 1);
}
}
/// A cheap single-threaded read-write lock pairing a `parking_lot::RwLock` type.
pub(crate) struct RwLock<T> {
lock: RawLock,
data: UnsafeCell<T>,
}
impl<T> RwLock<T> {
/// Creates a new `RwLock` containing the given value.
#[inline(always)]
pub(crate) fn new(value: T) -> Self {
RwLock {
lock: RawLock::new(UNUSED),
data: UnsafeCell::new(value),
}
}
/// Returns a reference to the underlying raw lock.
#[inline(always)]
pub(crate) unsafe fn raw(&self) -> &RawLock {
&self.lock
}
/// Returns a raw pointer to the underlying data.
#[inline(always)]
pub(crate) fn data_ptr(&self) -> *mut T {
self.data.get()
}
/// Consumes this `RwLock`, returning the underlying data.
#[inline(always)]
pub(crate) fn into_inner(self) -> T {
self.data.into_inner()
}
}
}
#[cfg(feature = "send")]
mod lock_impl {
use parking_lot::lock_api::RawRwLock;
pub(crate) type RawLock = parking_lot::RawRwLock;
pub(crate) use parking_lot::{RawRwLock as RawLock, RwLock};
impl super::UserDataLock for RawLock {
#[allow(clippy::declare_interior_mutable_const)]
const INIT: Self = <Self as parking_lot::lock_api::RawRwLock>::INIT;
#[inline(always)]
fn is_locked(&self) -> bool {
RawRwLock::is_locked(self)
parking_lot::lock_api::RawRwLock::is_locked(self)
}
#[inline(always)]
fn try_lock_shared(&self) -> bool {
RawRwLock::try_lock_shared(self)
parking_lot::lock_api::RawRwLock::try_lock_shared(self)
}
#[inline(always)]
fn try_lock_exclusive(&self) -> bool {
RawRwLock::try_lock_exclusive(self)
parking_lot::lock_api::RawRwLock::try_lock_exclusive(self)
}
#[inline(always)]
unsafe fn unlock_shared(&self) {
RawRwLock::unlock_shared(self)
parking_lot::lock_api::RawRwLock::unlock_shared(self)
}
#[inline(always)]
unsafe fn unlock_exclusive(&self) {
RawRwLock::unlock_exclusive(self)
parking_lot::lock_api::RawRwLock::unlock_exclusive(self)
}
}
}
+69 -7
View File
@@ -7,12 +7,11 @@ use crate::error::{Error, Result};
use crate::state::{Lua, RawLua};
use crate::traits::FromLua;
use crate::userdata::AnyUserData;
use crate::util::get_userdata;
use crate::util::{check_stack, get_userdata, take_userdata};
use crate::value::Value;
use super::cell::{UserDataStorage, UserDataVariant};
use super::lock::{LockGuard, RawLock, UserDataLock};
use super::util::is_sync;
#[cfg(feature = "userdata-wrappers")]
use {
@@ -63,11 +62,10 @@ impl<T> TryFrom<UserDataVariant<T>> for UserDataRef<T> {
#[inline]
fn try_from(variant: UserDataVariant<T>) -> Result<Self> {
let guard = if cfg!(not(feature = "send")) || is_sync::<T>() {
variant.raw_lock().try_lock_shared_guarded()
} else {
variant.raw_lock().try_lock_exclusive_guarded()
};
// Shared (read) lock is always correct:
// - with `send` feature, `T: Sync` is guaranteed by the `MaybeSync` bound on userdata creation
// - without `send` feature, single-threaded access makes shared lock safe for any `T`
let guard = variant.raw_lock().try_lock_shared_guarded();
let guard = guard.map_err(|_| Error::UserDataBorrowError)?;
let guard = unsafe { mem::transmute::<LockGuard<_>, LockGuard<'static, _>>(guard) };
Ok(UserDataRef::from_parts(UserDataRefInner::Default(variant), guard))
@@ -442,6 +440,66 @@ impl<T> DerefMut for UserDataRefMutInner<T> {
}
}
/// A wrapper type that takes ownership of a userdata value.
///
/// It implements [`FromLua`] and can be used to receive a typed userdata from Lua by taking
/// ownership of it.
/// The original Lua userdata is marked as destructed and cannot be used further.
pub struct UserDataOwned<T>(pub T);
impl<T> Deref for UserDataOwned<T> {
type Target = T;
#[inline]
fn deref(&self) -> &T {
&self.0
}
}
impl<T> DerefMut for UserDataOwned<T> {
#[inline]
fn deref_mut(&mut self) -> &mut T {
&mut self.0
}
}
impl<T: fmt::Debug> fmt::Debug for UserDataOwned<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
(**self).fmt(f)
}
}
impl<T: fmt::Display> fmt::Display for UserDataOwned<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
(**self).fmt(f)
}
}
impl<T: 'static> FromLua for UserDataOwned<T> {
fn from_lua(value: Value, _: &Lua) -> Result<Self> {
try_value_to_userdata::<T>(value)?.take().map(UserDataOwned)
}
unsafe fn from_stack(idx: c_int, lua: &RawLua) -> Result<Self> {
let state = lua.state();
let type_id = lua.get_userdata_type_id::<T>(state, idx)?;
match type_id {
Some(type_id) if type_id == TypeId::of::<T>() => {
let ud = get_userdata::<UserDataStorage<T>>(state, idx);
if (*ud).has_exclusive_access() {
check_stack(state, 1)?;
take_userdata::<UserDataStorage<T>>(state, idx)
.into_inner()
.map(UserDataOwned)
} else {
Err(Error::UserDataBorrowMutError)
}
}
_ => Err(Error::UserDataTypeMismatch),
}
}
}
#[inline]
fn try_value_to_userdata<T>(value: Value) -> Result<AnyUserData> {
match value {
@@ -466,6 +524,10 @@ mod assertions {
static_assertions::assert_impl_all!(UserDataRefMut<()>: Sync, Send);
#[cfg(feature = "send")]
static_assertions::assert_not_impl_all!(UserDataRefMut<std::rc::Rc<()>>: Send, Sync);
#[cfg(feature = "send")]
static_assertions::assert_impl_all!(UserDataOwned<()>: Send, Sync);
#[cfg(feature = "send")]
static_assertions::assert_not_impl_all!(UserDataOwned<std::rc::Rc<()>>: Send, Sync);
#[cfg(not(feature = "send"))]
static_assertions::assert_not_impl_all!(UserDataRef<()>: Send, Sync);
+6
View File
@@ -654,6 +654,12 @@ macro_rules! lua_userdata_impl {
// A special proxy object for UserData
pub(crate) struct UserDataProxy<T>(pub(crate) PhantomData<T>);
// `UserDataProxy` holds no real `T` value, only a type marker, so it is always safe to send/share.
#[cfg(feature = "send")]
unsafe impl<T> Send for UserDataProxy<T> {}
#[cfg(feature = "send")]
unsafe impl<T> Sync for UserDataProxy<T> {}
lua_userdata_impl!(UserDataProxy<T>);
#[cfg(all(feature = "userdata-wrappers", not(feature = "send")))]
-31
View File
@@ -1,6 +1,4 @@
use std::any::TypeId;
use std::cell::Cell;
use std::marker::PhantomData;
use std::os::raw::c_int;
use std::ptr;
@@ -11,35 +9,6 @@ use crate::error::{Error, Result};
use crate::types::CallbackPtr;
use crate::util::{get_userdata, rawget_field, rawset_field, take_userdata};
// This is a trick to check if a type is `Sync` or not.
// It uses leaked specialization feature from stdlib.
struct IsSync<'a, T> {
is_sync: &'a Cell<bool>,
_marker: PhantomData<T>,
}
impl<T> Clone for IsSync<'_, T> {
fn clone(&self) -> Self {
self.is_sync.set(false);
IsSync {
is_sync: self.is_sync,
_marker: PhantomData,
}
}
}
impl<T: Sync> Copy for IsSync<'_, T> {}
pub(crate) fn is_sync<T>() -> bool {
let is_sync = Cell::new(true);
let _ = [IsSync::<T> {
is_sync: &is_sync,
_marker: PhantomData,
}]
.clone();
is_sync.get()
}
// Userdata type hints, used to match types of wrapped userdata
#[derive(Clone, Copy)]
pub(crate) struct TypeIdHints {
+1 -1
View File
@@ -197,7 +197,7 @@ where
F: FnOnce(*mut ffi::lua_State) -> R,
R: Copy,
{
struct Params<F, R: Copy> {
struct Params<F, R> {
function: Option<F>,
result: MaybeUninit<R>,
nresults: c_int,
+5 -13
View File
@@ -151,7 +151,7 @@ impl Value {
/// This might invoke the `__tostring` metamethod for non-primitive types (eg. tables,
/// functions).
pub fn to_string(&self) -> Result<String> {
unsafe fn invoke_to_string(vref: &ValueRef) -> Result<String> {
unsafe fn invoke_tostring(vref: &ValueRef) -> Result<String> {
let lua = vref.lua.lock();
let state = lua.state();
let _guard = StackGuard::new(state);
@@ -178,9 +178,9 @@ impl Value {
| Value::Function(Function(vref))
| Value::Thread(Thread(vref, ..))
| Value::UserData(AnyUserData(vref))
| Value::Other(vref) => unsafe { invoke_to_string(vref) },
| Value::Other(vref) => unsafe { invoke_tostring(vref) },
#[cfg(feature = "luau")]
Value::Buffer(crate::Buffer(vref)) => unsafe { invoke_to_string(vref) },
Value::Buffer(crate::Buffer(vref)) => unsafe { invoke_tostring(vref) },
Value::Error(err) => Ok(err.to_string()),
}
}
@@ -361,7 +361,7 @@ impl Value {
note = "This method does not follow Rust naming convention. Use `as_string().and_then(|s| s.to_str().ok())` instead."
)]
#[inline]
pub fn as_str(&self) -> Option<BorrowedStr<'_>> {
pub fn as_str(&self) -> Option<BorrowedStr> {
self.as_string().and_then(|s| s.to_str().ok())
}
@@ -561,15 +561,7 @@ impl Value {
t @ Value::Table(_) => write!(fmt, "table: {:?}", t.to_pointer()),
f @ Value::Function(_) => write!(fmt, "function: {:?}", f.to_pointer()),
t @ Value::Thread(_) => write!(fmt, "thread: {:?}", t.to_pointer()),
u @ Value::UserData(ud) => {
// Try `__name/__type` first then `__tostring`
let name = ud.type_name().ok().flatten();
let s = name
.map(|name| format!("{name}: {:?}", u.to_pointer()))
.or_else(|| u.to_string().ok())
.unwrap_or_else(|| format!("userdata: {:?}", u.to_pointer()));
write!(fmt, "{s}")
}
Value::UserData(ud) => ud.fmt_pretty(fmt),
#[cfg(feature = "luau")]
buf @ Value::Buffer(_) => write!(fmt, "buffer: {:?}", buf.to_pointer()),
Value::Error(e) if recursive => write!(fmt, "{e:?}"),
+3 -3
View File
@@ -7,7 +7,7 @@ use futures_util::stream::TryStreamExt;
use tokio::sync::Mutex;
use mlua::{
Error, Function, Lua, LuaOptions, MultiValue, ObjectLike, Result, StdLib, Table, ThreadStatus, UserData,
Error, Function, Lua, LuaOptions, MultiValue, ObjectLike, Result, StdLib, Table, UserData,
UserDataMethods, UserDataRef, Value,
};
@@ -41,7 +41,7 @@ async fn test_async_function_wrap() -> Result<()> {
let f = Function::wrap_async(|s: String| async move {
tokio::task::yield_now().await;
Ok(s)
Ok::<_, Error>(s)
});
lua.globals().set("f", f)?;
let res: String = lua.load(r#"f("hello")"#).eval_async().await?;
@@ -714,7 +714,7 @@ fn test_async_yield_with() -> Result<()> {
assert_eq!(thread.resume::<(i32, i32)>((10, 11))?, (21, 110));
assert_eq!(thread.resume::<(i32, i32)>((11, 12))?, (23, 132));
assert_eq!(thread.resume::<(i32, i32)>((12, 13))?, (0, 0));
assert_eq!(thread.status(), ThreadStatus::Finished);
assert!(thread.is_finished());
Ok(())
}
+2 -1
View File
@@ -1,3 +1,4 @@
#[cfg(not(target_os = "wasi"))]
use std::{fs, io};
use mlua::{Chunk, ChunkMode, Lua, Result};
@@ -85,7 +86,7 @@ fn test_chunk_macro() -> Result<()> {
data.raw_set("num", 1)?;
let ud = mlua::AnyUserData::wrap("hello");
let f = mlua::Function::wrap(|| Ok(()));
let f = mlua::Function::wrap(|| Ok::<_, mlua::Error>(()));
lua.globals().set("g", 123)?;
+11 -11
View File
@@ -1,28 +1,28 @@
error[E0277]: the type `UnsafeCell<mlua::state::raw::RawLua>` may contain interior mutability and a reference may not be safely transferable across a catch_unwind boundary
error[E0277]: the type `UnsafeCell<mlua::state::RawLua>` may contain interior mutability and a reference may not be safely transferable across a catch_unwind boundary
--> tests/compile/lua_norefunwindsafe.rs:7:18
|
7 | catch_unwind(|| lua.create_table().unwrap());
| ------------ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `UnsafeCell<mlua::state::raw::RawLua>` may contain interior mutability and a reference may not be safely transferable across a catch_unwind boundary
| ------------ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `UnsafeCell<mlua::state::RawLua>` may contain interior mutability and a reference may not be safely transferable across a catch_unwind boundary
| |
| required by a bound introduced by this call
|
= help: within `Lua`, the trait `RefUnwindSafe` is not implemented for `UnsafeCell<mlua::state::raw::RawLua>`
note: required because it appears within the type `lock_api::remutex::ReentrantMutex<parking_lot::raw_mutex::RawMutex, parking_lot::remutex::RawThreadId, mlua::state::raw::RawLua>`
= help: within `Lua`, the trait `RefUnwindSafe` is not implemented for `UnsafeCell<mlua::state::RawLua>`
note: required because it appears within the type `lock_api::remutex::ReentrantMutex<parking_lot::raw_mutex::RawMutex, parking_lot::remutex::RawThreadId, mlua::state::RawLua>`
--> $CARGO/lock_api-$VERSION/src/remutex.rs
|
| pub struct ReentrantMutex<R, G, T: ?Sized> {
| ^^^^^^^^^^^^^^
note: required because it appears within the type `alloc::sync::ArcInner<lock_api::remutex::ReentrantMutex<parking_lot::raw_mutex::RawMutex, parking_lot::remutex::RawThreadId, mlua::state::raw::RawLua>>`
note: required because it appears within the type `alloc::sync::ArcInner<lock_api::remutex::ReentrantMutex<parking_lot::raw_mutex::RawMutex, parking_lot::remutex::RawThreadId, mlua::state::RawLua>>`
--> $RUST/alloc/src/sync.rs
|
| struct ArcInner<T: ?Sized> {
| ^^^^^^^^
note: required because it appears within the type `PhantomData<alloc::sync::ArcInner<lock_api::remutex::ReentrantMutex<parking_lot::raw_mutex::RawMutex, parking_lot::remutex::RawThreadId, mlua::state::raw::RawLua>>>`
note: required because it appears within the type `PhantomData<alloc::sync::ArcInner<lock_api::remutex::ReentrantMutex<parking_lot::raw_mutex::RawMutex, parking_lot::remutex::RawThreadId, mlua::state::RawLua>>>`
--> $RUST/core/src/marker.rs
|
| pub struct PhantomData<T: PointeeSized>;
| ^^^^^^^^^^^
note: required because it appears within the type `Arc<lock_api::remutex::ReentrantMutex<parking_lot::raw_mutex::RawMutex, parking_lot::remutex::RawThreadId, mlua::state::raw::RawLua>>`
note: required because it appears within the type `Arc<lock_api::remutex::ReentrantMutex<parking_lot::raw_mutex::RawMutex, parking_lot::remutex::RawThreadId, mlua::state::RawLua>>`
--> $RUST/alloc/src/sync.rs
|
| pub struct Arc<
@@ -63,22 +63,22 @@ note: required because it appears within the type `lock_api::remutex::RawReentra
|
| pub struct RawReentrantMutex<R, G> {
| ^^^^^^^^^^^^^^^^^
note: required because it appears within the type `lock_api::remutex::ReentrantMutex<parking_lot::raw_mutex::RawMutex, parking_lot::remutex::RawThreadId, mlua::state::raw::RawLua>`
note: required because it appears within the type `lock_api::remutex::ReentrantMutex<parking_lot::raw_mutex::RawMutex, parking_lot::remutex::RawThreadId, mlua::state::RawLua>`
--> $CARGO/lock_api-$VERSION/src/remutex.rs
|
| pub struct ReentrantMutex<R, G, T: ?Sized> {
| ^^^^^^^^^^^^^^
note: required because it appears within the type `alloc::sync::ArcInner<lock_api::remutex::ReentrantMutex<parking_lot::raw_mutex::RawMutex, parking_lot::remutex::RawThreadId, mlua::state::raw::RawLua>>`
note: required because it appears within the type `alloc::sync::ArcInner<lock_api::remutex::ReentrantMutex<parking_lot::raw_mutex::RawMutex, parking_lot::remutex::RawThreadId, mlua::state::RawLua>>`
--> $RUST/alloc/src/sync.rs
|
| struct ArcInner<T: ?Sized> {
| ^^^^^^^^
note: required because it appears within the type `PhantomData<alloc::sync::ArcInner<lock_api::remutex::ReentrantMutex<parking_lot::raw_mutex::RawMutex, parking_lot::remutex::RawThreadId, mlua::state::raw::RawLua>>>`
note: required because it appears within the type `PhantomData<alloc::sync::ArcInner<lock_api::remutex::ReentrantMutex<parking_lot::raw_mutex::RawMutex, parking_lot::remutex::RawThreadId, mlua::state::RawLua>>>`
--> $RUST/core/src/marker.rs
|
| pub struct PhantomData<T: PointeeSized>;
| ^^^^^^^^^^^
note: required because it appears within the type `Arc<lock_api::remutex::ReentrantMutex<parking_lot::raw_mutex::RawMutex, parking_lot::remutex::RawThreadId, mlua::state::raw::RawLua>>`
note: required because it appears within the type `Arc<lock_api::remutex::ReentrantMutex<parking_lot::raw_mutex::RawMutex, parking_lot::remutex::RawThreadId, mlua::state::RawLua>>`
--> $RUST/alloc/src/sync.rs
|
| pub struct Arc<
+12 -12
View File
@@ -1,24 +1,24 @@
error[E0277]: the type `UnsafeCell<mlua::state::raw::RawLua>` may contain interior mutability and a reference may not be safely transferable across a catch_unwind boundary
error[E0277]: the type `UnsafeCell<mlua::state::RawLua>` may contain interior mutability and a reference may not be safely transferable across a catch_unwind boundary
--> tests/compile/ref_nounwindsafe.rs:8:18
|
8 | catch_unwind(move || table.set("a", "b").unwrap());
| ------------ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `UnsafeCell<mlua::state::raw::RawLua>` may contain interior mutability and a reference may not be safely transferable across a catch_unwind boundary
| ------------ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `UnsafeCell<mlua::state::RawLua>` may contain interior mutability and a reference may not be safely transferable across a catch_unwind boundary
| |
| required by a bound introduced by this call
|
= help: within `alloc::sync::ArcInner<lock_api::remutex::ReentrantMutex<parking_lot::raw_mutex::RawMutex, parking_lot::remutex::RawThreadId, mlua::state::raw::RawLua>>`, the trait `RefUnwindSafe` is not implemented for `UnsafeCell<mlua::state::raw::RawLua>`
note: required because it appears within the type `lock_api::remutex::ReentrantMutex<parking_lot::raw_mutex::RawMutex, parking_lot::remutex::RawThreadId, mlua::state::raw::RawLua>`
= help: within `alloc::sync::ArcInner<lock_api::remutex::ReentrantMutex<parking_lot::raw_mutex::RawMutex, parking_lot::remutex::RawThreadId, mlua::state::RawLua>>`, the trait `RefUnwindSafe` is not implemented for `UnsafeCell<mlua::state::RawLua>`
note: required because it appears within the type `lock_api::remutex::ReentrantMutex<parking_lot::raw_mutex::RawMutex, parking_lot::remutex::RawThreadId, mlua::state::RawLua>`
--> $CARGO/lock_api-$VERSION/src/remutex.rs
|
| pub struct ReentrantMutex<R, G, T: ?Sized> {
| ^^^^^^^^^^^^^^
note: required because it appears within the type `alloc::sync::ArcInner<lock_api::remutex::ReentrantMutex<parking_lot::raw_mutex::RawMutex, parking_lot::remutex::RawThreadId, mlua::state::raw::RawLua>>`
note: required because it appears within the type `alloc::sync::ArcInner<lock_api::remutex::ReentrantMutex<parking_lot::raw_mutex::RawMutex, parking_lot::remutex::RawThreadId, mlua::state::RawLua>>`
--> $RUST/alloc/src/sync.rs
|
| struct ArcInner<T: ?Sized> {
| ^^^^^^^^
= note: required for `NonNull<alloc::sync::ArcInner<lock_api::remutex::ReentrantMutex<parking_lot::raw_mutex::RawMutex, parking_lot::remutex::RawThreadId, mlua::state::raw::RawLua>>>` to implement `UnwindSafe`
note: required because it appears within the type `std::sync::Weak<lock_api::remutex::ReentrantMutex<parking_lot::raw_mutex::RawMutex, parking_lot::remutex::RawThreadId, mlua::state::raw::RawLua>>`
= note: required for `NonNull<alloc::sync::ArcInner<lock_api::remutex::ReentrantMutex<parking_lot::raw_mutex::RawMutex, parking_lot::remutex::RawThreadId, mlua::state::RawLua>>>` to implement `UnwindSafe`
note: required because it appears within the type `std::sync::Weak<lock_api::remutex::ReentrantMutex<parking_lot::raw_mutex::RawMutex, parking_lot::remutex::RawThreadId, mlua::state::RawLua>>`
--> $RUST/alloc/src/sync.rs
|
| pub struct Weak<
@@ -57,7 +57,7 @@ error[E0277]: the type `UnsafeCell<usize>` may contain interior mutability and a
| |
| required by a bound introduced by this call
|
= help: within `alloc::sync::ArcInner<lock_api::remutex::ReentrantMutex<parking_lot::raw_mutex::RawMutex, parking_lot::remutex::RawThreadId, mlua::state::raw::RawLua>>`, the trait `RefUnwindSafe` is not implemented for `UnsafeCell<usize>`
= help: within `alloc::sync::ArcInner<lock_api::remutex::ReentrantMutex<parking_lot::raw_mutex::RawMutex, parking_lot::remutex::RawThreadId, mlua::state::RawLua>>`, the trait `RefUnwindSafe` is not implemented for `UnsafeCell<usize>`
note: required because it appears within the type `Cell<usize>`
--> $RUST/core/src/cell.rs
|
@@ -68,18 +68,18 @@ note: required because it appears within the type `lock_api::remutex::RawReentra
|
| pub struct RawReentrantMutex<R, G> {
| ^^^^^^^^^^^^^^^^^
note: required because it appears within the type `lock_api::remutex::ReentrantMutex<parking_lot::raw_mutex::RawMutex, parking_lot::remutex::RawThreadId, mlua::state::raw::RawLua>`
note: required because it appears within the type `lock_api::remutex::ReentrantMutex<parking_lot::raw_mutex::RawMutex, parking_lot::remutex::RawThreadId, mlua::state::RawLua>`
--> $CARGO/lock_api-$VERSION/src/remutex.rs
|
| pub struct ReentrantMutex<R, G, T: ?Sized> {
| ^^^^^^^^^^^^^^
note: required because it appears within the type `alloc::sync::ArcInner<lock_api::remutex::ReentrantMutex<parking_lot::raw_mutex::RawMutex, parking_lot::remutex::RawThreadId, mlua::state::raw::RawLua>>`
note: required because it appears within the type `alloc::sync::ArcInner<lock_api::remutex::ReentrantMutex<parking_lot::raw_mutex::RawMutex, parking_lot::remutex::RawThreadId, mlua::state::RawLua>>`
--> $RUST/alloc/src/sync.rs
|
| struct ArcInner<T: ?Sized> {
| ^^^^^^^^
= note: required for `NonNull<alloc::sync::ArcInner<lock_api::remutex::ReentrantMutex<parking_lot::raw_mutex::RawMutex, parking_lot::remutex::RawThreadId, mlua::state::raw::RawLua>>>` to implement `UnwindSafe`
note: required because it appears within the type `std::sync::Weak<lock_api::remutex::ReentrantMutex<parking_lot::raw_mutex::RawMutex, parking_lot::remutex::RawThreadId, mlua::state::raw::RawLua>>`
= note: required for `NonNull<alloc::sync::ArcInner<lock_api::remutex::ReentrantMutex<parking_lot::raw_mutex::RawMutex, parking_lot::remutex::RawThreadId, mlua::state::RawLua>>>` to implement `UnwindSafe`
note: required because it appears within the type `std::sync::Weak<lock_api::remutex::ReentrantMutex<parking_lot::raw_mutex::RawMutex, parking_lot::remutex::RawThreadId, mlua::state::RawLua>>`
--> $RUST/alloc/src/sync.rs
|
| pub struct Weak<
+13
View File
@@ -77,6 +77,19 @@ fn test_error_chain() -> Result<()> {
Ok(())
}
#[test]
fn test_external_error() {
// `Error::external` should preserve `mlua::Error`
let runtime_err = Error::runtime("test error");
let converted = Error::external(runtime_err);
assert!(matches!(converted, Error::RuntimeError(ref msg) if msg == "test error"));
// Other errors should become `ExternalError`
let converted = Error::external(io::Error::other("other error"));
assert!(matches!(converted, Error::ExternalError(_)));
assert!(converted.downcast_ref::<io::Error>().is_some());
}
#[cfg(feature = "anyhow")]
#[test]
fn test_error_anyhow() -> Result<()> {
+35 -3
View File
@@ -1,3 +1,6 @@
use std::fmt;
use std::result::Result as StdResult;
use mlua::{Error, Function, Lua, LuaString, Result, Table, Variadic};
#[test]
@@ -343,7 +346,7 @@ fn test_function_deep_clone() -> Result<()> {
fn test_function_wrap() -> Result<()> {
let lua = Lua::new();
let f = Function::wrap(|s: LuaString, n| Ok(s.to_str().unwrap().repeat(n)));
let f = Function::wrap(|s: LuaString, n| Ok::<_, Error>(s.to_str().unwrap().repeat(n)));
lua.globals().set("f", f)?;
lua.load(r#"assert(f("hello", 2) == "hellohello")"#)
.exec()
@@ -361,11 +364,40 @@ fn test_function_wrap() -> Result<()> {
.exec()
.unwrap();
// Return external error
#[derive(Debug)]
struct MyError(String);
impl fmt::Display for MyError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "MyError: {}", self.0)
}
}
impl std::error::Error for MyError {}
let fext = Function::wrap(|s: String| -> StdResult<String, MyError> {
if s == "bad" {
return Err(MyError("bad input".into()));
}
Ok(format!("ok: {s}"))
});
lua.globals().set("fext", fext)?;
lua.load(r#"assert(fext("hello") == "ok: hello")"#)
.exec()
.unwrap();
lua.load(
r#"
local ok, err = pcall(fext, "bad")
assert(not ok and tostring(err):find("MyError: bad input"))
"#,
)
.exec()
.unwrap();
// Mutable callback
let mut i = 0;
let fmut = Function::wrap_mut(move || {
i += 1;
Ok(i)
Ok::<_, Error>(i)
});
lua.globals().set("fmut", fmut)?;
lua.load(r#"fmut(); fmut(); assert(fmut() == 3)"#).exec().unwrap();
@@ -385,7 +417,7 @@ fn test_function_wrap() -> Result<()> {
// Check recursive mut callback error
let fmut = Function::wrap_mut(|f: Function| match f.call::<()>(&f) {
Err(Error::CallbackError { cause, .. }) => match cause.as_ref() {
Error::RecursiveMutCallback { .. } => Ok(()),
Error::RecursiveMutCallback { .. } => Ok::<_, Error>(()),
other => panic!("incorrect result: {other:?}"),
},
other => panic!("incorrect result: {other:?}"),
+5 -4
View File
@@ -3,7 +3,8 @@
use std::sync::atomic::{AtomicI64, Ordering};
use std::sync::{Arc, Mutex};
use mlua::{DebugEvent, Error, HookTriggers, Lua, Result, ThreadStatus, Value, VmState};
use mlua::debug::DebugEvent;
use mlua::{Error, HookTriggers, Lua, Result, Value, VmState};
#[test]
fn test_hook_triggers() {
@@ -280,14 +281,14 @@ fn test_hook_yield() -> Result<()> {
assert!(co.resume::<()>(()).is_ok());
assert!(co.resume::<()>(()).is_ok());
assert!(co.resume::<()>(()).is_ok());
assert!(co.status() == ThreadStatus::Finished);
assert!(co.is_finished());
}
#[cfg(any(feature = "lua51", feature = "lua52", feature = "luajit"))]
{
assert!(
matches!(co.resume::<()>(()), Err(Error::RuntimeError(err)) if err.contains("attempt to yield from a hook"))
);
assert!(co.status() == ThreadStatus::Error);
assert!(co.is_error());
}
Ok(())
@@ -320,7 +321,7 @@ fn test_global_hook() -> Result<()> {
thread.resume::<()>(()).unwrap();
lua.remove_global_hook();
thread.resume::<()>(()).unwrap();
assert_eq!(thread.status(), ThreadStatus::Finished);
assert!(thread.is_finished());
assert_eq!(counter.load(Ordering::Relaxed), 3);
Ok(())
+21 -3
View File
@@ -7,7 +7,7 @@ use std::sync::Arc;
use std::sync::atomic::{AtomicBool, AtomicPtr, AtomicU64, Ordering};
use mlua::{
Compiler, Error, Function, Lua, LuaOptions, Result, StdLib, Table, ThreadStatus, Value, Vector, VmState,
Compiler, Error, Function, Lua, LuaOptions, ObjectLike, Result, StdLib, Table, Value, Vector, VmState,
};
#[test]
@@ -324,11 +324,11 @@ fn test_interrupts() -> Result<()> {
.into_function()?,
)?;
co.resume::<()>(())?;
assert_eq!(co.status(), ThreadStatus::Resumable);
assert!(co.is_resumable());
let result: i32 = co.resume(())?;
assert_eq!(result, 6);
assert_eq!(yield_count.load(Ordering::Relaxed), 7);
assert_eq!(co.status(), ThreadStatus::Finished);
assert!(co.is_finished());
// Test no yielding at non-yieldable points
yield_count.store(0, Ordering::Relaxed);
@@ -535,5 +535,23 @@ fn test_heap_dump() -> Result<()> {
Ok(())
}
#[test]
fn test_integer64_type() -> Result<()> {
let lua = Lua::new();
_ = Lua::set_fflag("LuauIntegerType", true);
let integer_lib = lua.globals().get::<Table>("integer")?;
let n = integer_lib.call_function::<i64>("create", 42)?;
assert_eq!(n, 42);
let n: i64 = lua.load("return 42i").eval()?;
assert_eq!(n, 42);
let n: i64 = lua.load("return -42i").eval()?;
assert_eq!(n, -42);
Ok(())
}
#[path = "luau/require.rs"]
mod require;
+4 -5
View File
@@ -1,7 +1,8 @@
use std::io::Result as IoResult;
use std::result::Result as StdResult;
use mlua::{Error, FromLua, IntoLua, Lua, MultiValue, NavigateError, Require, Result, TextRequirer, Value};
use mlua::luau::{FsRequirer, NavigateError, Require};
use mlua::{Error, FromLua, IntoLua, Lua, MultiValue, Result, Value};
fn run_require(lua: &Lua, path: impl IntoLua) -> Result<Value> {
lua.load(r#"return require(...)"#).call(path)
@@ -65,7 +66,7 @@ fn test_require_errors() {
assert!((res.unwrap_err().to_string()).contains("@ is not a valid alias"));
// Test throwing mlua::Error
struct MyRequire(TextRequirer);
struct MyRequire(FsRequirer);
impl Require for MyRequire {
fn is_require_allowed(&self, chunk_name: &str) -> bool {
@@ -109,9 +110,7 @@ fn test_require_errors() {
}
}
let require = lua
.create_require_function(MyRequire(TextRequirer::new()))
.unwrap();
let require = lua.create_require_function(MyRequire(FsRequirer::new())).unwrap();
lua.globals().set("require", require).unwrap();
let res = lua.load(r#"return require('./a/relative/path')"#).exec();
assert!((res.unwrap_err().to_string()).contains("test error"));
+24 -4
View File
@@ -1,6 +1,10 @@
use std::sync::Arc;
use mlua::{Error, GCMode, Lua, Result, UserData};
use mlua::state::{GcIncParams, GcMode};
use mlua::{Error, Lua, Result, UserData};
#[cfg(any(feature = "lua54", feature = "lua55"))]
use mlua::state::GcGenParams;
#[test]
fn test_memory_limit() -> Result<()> {
@@ -74,8 +78,14 @@ fn test_gc_control() -> Result<()> {
#[cfg(any(feature = "lua55", feature = "lua54"))]
{
assert_eq!(lua.gc_gen(0, 0), GCMode::Incremental);
assert_eq!(lua.gc_inc(0, 0, 0), GCMode::Generational);
assert!(matches!(
lua.gc_set_mode(GcMode::Generational(GcGenParams::default())),
GcMode::Incremental(_)
));
assert!(matches!(
lua.gc_set_mode(GcMode::Incremental(GcIncParams::default())),
GcMode::Generational(_)
));
}
#[cfg(any(
@@ -93,7 +103,17 @@ fn test_gc_control() -> Result<()> {
assert!(lua.gc_is_running());
}
assert_eq!(lua.gc_inc(200, 100, 13), GCMode::Incremental);
assert!(matches!(
lua.gc_set_mode(GcMode::Incremental({
let p = GcIncParams::default().step_multiplier(100);
#[cfg(not(feature = "luau"))]
let p = p.pause(200);
#[cfg(feature = "luau")]
let p = p.goal(200);
p
})),
GcMode::Incremental(_)
));
struct MyUserdata(#[allow(unused)] Arc<()>);
impl UserData for MyUserdata {}
+4 -49
View File
@@ -1,50 +1,7 @@
#![cfg(feature = "send")]
use std::cell::UnsafeCell;
use std::marker::PhantomData;
use mlua::{AnyUserData, Error, Lua, ObjectLike, Result, UserData, UserDataMethods, UserDataRef};
use static_assertions::{assert_impl_all, assert_not_impl_all};
#[test]
fn test_userdata_multithread_access_send_only() -> Result<()> {
let lua = Lua::new();
// This type is `Send` but not `Sync`.
struct MyUserData(String, PhantomData<UnsafeCell<()>>);
assert_impl_all!(MyUserData: Send);
assert_not_impl_all!(MyUserData: Sync);
impl UserData for MyUserData {
fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {
methods.add_method("method", |lua, this, ()| {
let ud = lua.globals().get::<AnyUserData>("ud")?;
assert_eq!(ud.call_method::<String>("method2", ())?, "method2");
Ok(this.0.clone())
});
methods.add_method("method2", |_, _, ()| Ok("method2"));
}
}
lua.globals()
.set("ud", MyUserData("hello".to_string(), PhantomData))?;
// We acquired the exclusive reference.
let ud = lua.globals().get::<UserDataRef<MyUserData>>("ud")?;
std::thread::scope(|s| {
s.spawn(|| {
let res = lua.globals().get::<UserDataRef<MyUserData>>("ud");
assert!(matches!(res, Err(Error::UserDataBorrowError)));
});
});
drop(ud);
lua.load("ud:method()").exec().unwrap();
Ok(())
}
use mlua::{AnyUserData, Lua, ObjectLike, Result, UserData, UserDataMethods, UserDataRef};
use static_assertions::assert_impl_all;
#[test]
fn test_userdata_multithread_access_sync() -> Result<()> {
@@ -74,13 +31,11 @@ fn test_userdata_multithread_access_sync() -> Result<()> {
std::thread::scope(|s| {
s.spawn(|| {
// Getting another shared reference for `Sync` type is allowed.
// FIXME: does not work due to https://github.com/rust-lang/rust/pull/135634
// let _ = lua.globals().get::<UserDataRef<MyUserData>>("ud").unwrap();
let _ = lua.globals().get::<UserDataRef<MyUserData>>("ud").unwrap();
});
});
// FIXME: does not work due to https://github.com/rust-lang/rust/pull/135634
// lua.load("ud:method()").exec().unwrap();
lua.load("ud:method()").exec().unwrap();
Ok(())
}
+2 -2
View File
@@ -1374,7 +1374,7 @@ fn test_inspect_stack() -> Result<()> {
local function baz(a, b, c, ...)
return stack_info()
end
assert(baz() == 'DebugStack { num_ups: 1, num_params: 3, is_vararg: true }')
assert(baz() == 'DebugStack { num_upvalues: 1, num_params: 3, is_vararg: true }')
"#,
)
.exec()?;
@@ -1387,7 +1387,7 @@ fn test_inspect_stack() -> Result<()> {
local function baz(a, b, c, ...)
return stack_info()
end
assert(baz() == 'DebugStack { num_ups: 1 }')
assert(baz() == 'DebugStack { num_upvalues: 1 }')
"#,
)
.exec()?;
+18 -18
View File
@@ -1,6 +1,6 @@
use std::panic::catch_unwind;
use mlua::{Error, Function, IntoLua, Lua, Result, Thread, ThreadStatus, Value};
use mlua::{Error, Function, IntoLua, Lua, Result, Thread, Value};
#[test]
fn test_thread() -> Result<()> {
@@ -21,17 +21,17 @@ fn test_thread() -> Result<()> {
.eval()?,
)?;
assert_eq!(thread.status(), ThreadStatus::Resumable);
assert!(thread.is_resumable());
assert_eq!(thread.resume::<i64>(0)?, 0);
assert_eq!(thread.status(), ThreadStatus::Resumable);
assert!(thread.is_resumable());
assert_eq!(thread.resume::<i64>(1)?, 1);
assert_eq!(thread.status(), ThreadStatus::Resumable);
assert!(thread.is_resumable());
assert_eq!(thread.resume::<i64>(2)?, 3);
assert_eq!(thread.status(), ThreadStatus::Resumable);
assert!(thread.is_resumable());
assert_eq!(thread.resume::<i64>(3)?, 6);
assert_eq!(thread.status(), ThreadStatus::Resumable);
assert!(thread.is_resumable());
assert_eq!(thread.resume::<i64>(4)?, 10);
assert_eq!(thread.status(), ThreadStatus::Finished);
assert!(thread.is_finished());
let accumulate = lua.create_thread(
lua.load(
@@ -50,9 +50,9 @@ fn test_thread() -> Result<()> {
accumulate.resume::<()>(i)?;
}
assert_eq!(accumulate.resume::<i64>(4)?, 10);
assert_eq!(accumulate.status(), ThreadStatus::Resumable);
assert!(accumulate.is_resumable());
assert!(accumulate.resume::<()>("error").is_err());
assert_eq!(accumulate.status(), ThreadStatus::Error);
assert!(accumulate.is_error());
let thread = lua
.load(
@@ -65,7 +65,7 @@ fn test_thread() -> Result<()> {
"#,
)
.eval::<Thread>()?;
assert_eq!(thread.status(), ThreadStatus::Resumable);
assert!(thread.is_resumable());
assert_eq!(thread.resume::<i64>(())?, 42);
let thread: Thread = lua
@@ -92,7 +92,7 @@ fn test_thread() -> Result<()> {
// Already running thread must be unresumable
let thread = lua.create_thread(lua.create_function(|lua, ()| {
assert_eq!(lua.current_thread().status(), ThreadStatus::Running);
assert!(lua.current_thread().is_running());
let result = lua.current_thread().resume::<()>(());
assert!(
matches!(result, Err(Error::CoroutineUnresumable)),
@@ -123,12 +123,12 @@ fn test_thread_reset() -> Result<()> {
assert!(thread.reset(func.clone()).is_ok());
for _ in 0..2 {
assert_eq!(thread.status(), ThreadStatus::Resumable);
assert!(thread.is_resumable());
let _ = thread.resume::<AnyUserData>(MyUserData(arc.clone()))?;
assert_eq!(thread.status(), ThreadStatus::Resumable);
assert!(thread.is_resumable());
assert_eq!(Arc::strong_count(&arc), 2);
thread.resume::<()>(())?;
assert_eq!(thread.status(), ThreadStatus::Finished);
assert!(thread.is_finished());
thread.reset(func.clone())?;
lua.gc_collect()?;
assert_eq!(Arc::strong_count(&arc), 1);
@@ -138,21 +138,21 @@ fn test_thread_reset() -> Result<()> {
let func: Function = lua.load(r#"function(ud) error("test error") end"#).eval()?;
let thread = lua.create_thread(func.clone())?;
let _ = thread.resume::<AnyUserData>(MyUserData(arc.clone()));
assert_eq!(thread.status(), ThreadStatus::Error);
assert!(thread.is_error());
assert_eq!(Arc::strong_count(&arc), 2);
#[cfg(any(feature = "lua55", feature = "lua54"))]
{
assert!(thread.reset(func.clone()).is_err());
// Reset behavior has changed in Lua v5.4.4
// It's became possible to force reset thread by popping error object
assert!(matches!(thread.status(), ThreadStatus::Finished));
assert!(thread.is_finished());
assert!(thread.reset(func.clone()).is_ok());
assert_eq!(thread.status(), ThreadStatus::Resumable);
assert!(thread.is_resumable());
}
#[cfg(any(feature = "lua55", feature = "lua54", feature = "luau"))]
{
assert!(thread.reset(func.clone()).is_ok());
assert_eq!(thread.status(), ThreadStatus::Resumable);
assert!(thread.is_resumable());
}
// Try reset running thread
+10 -7
View File
@@ -1,6 +1,6 @@
use std::os::raw::c_void;
use mlua::{Function, LightUserData, Lua, LuaString, Number, Result, Thread};
use mlua::{Error, Function, LightUserData, Lua, LuaString, Number, Result, Thread};
#[test]
fn test_lightuserdata() -> Result<()> {
@@ -30,7 +30,7 @@ fn test_boolean_type_metatable() -> Result<()> {
let lua = Lua::new();
let mt = lua.create_table()?;
mt.set("__add", Function::wrap(|a, b| Ok(a || b)))?;
mt.set("__add", Function::wrap(|a, b| Ok::<_, mlua::Error>(a || b)))?;
assert_eq!(lua.type_metatable::<bool>(), None);
lua.set_type_metatable::<bool>(Some(mt.clone()));
assert_eq!(lua.type_metatable::<bool>().unwrap(), mt);
@@ -51,7 +51,7 @@ fn test_lightuserdata_type_metatable() -> Result<()> {
mt.set(
"__add",
Function::wrap(|a: LightUserData, b: LightUserData| {
Ok(LightUserData((a.0 as usize + b.0 as usize) as *mut c_void))
Ok::<_, Error>(LightUserData((a.0 as usize + b.0 as usize) as *mut c_void))
}),
)?;
lua.set_type_metatable::<LightUserData>(Some(mt.clone()));
@@ -79,7 +79,10 @@ fn test_number_type_metatable() -> Result<()> {
let lua = Lua::new();
let mt = lua.create_table()?;
mt.set("__call", Function::wrap(|n1: f64, n2: f64| Ok(n1 * n2)))?;
mt.set(
"__call",
Function::wrap(|n1: f64, n2: f64| Ok::<_, Error>(n1 * n2)),
)?;
lua.set_type_metatable::<Number>(Some(mt.clone()));
assert_eq!(lua.type_metatable::<Number>().unwrap(), mt);
@@ -96,7 +99,7 @@ fn test_string_type_metatable() -> Result<()> {
let mt = lua.create_table()?;
mt.set(
"__add",
Function::wrap(|a: String, b: String| Ok(format!("{a}{b}"))),
Function::wrap(|a: String, b: String| Ok::<_, Error>(format!("{a}{b}"))),
)?;
lua.set_type_metatable::<LuaString>(Some(mt.clone()));
assert_eq!(lua.type_metatable::<LuaString>().unwrap(), mt);
@@ -113,7 +116,7 @@ fn test_function_type_metatable() -> Result<()> {
let mt = lua.create_table()?;
mt.set(
"__index",
Function::wrap(|_: Function, key: String| Ok(format!("function.{key}"))),
Function::wrap(|_: Function, key: String| Ok::<_, Error>(format!("function.{key}"))),
)?;
lua.set_type_metatable::<Function>(Some(mt.clone()));
assert_eq!(lua.type_metatable::<Function>(), Some(mt));
@@ -132,7 +135,7 @@ fn test_thread_type_metatable() -> Result<()> {
let mt = lua.create_table()?;
mt.set(
"__index",
Function::wrap(|_: Thread, key: String| Ok(format!("thread.{key}"))),
Function::wrap(|_: Thread, key: String| Ok::<_, Error>(format!("thread.{key}"))),
)?;
lua.set_type_metatable::<Thread>(Some(mt.clone()));
assert_eq!(lua.type_metatable::<Thread>(), Some(mt));
+85 -2
View File
@@ -7,7 +7,7 @@ use std::sync::atomic::{AtomicI64, Ordering};
use mlua::{
AnyUserData, Error, ExternalError, Function, Lua, LuaString, MetaMethod, Nil, ObjectLike, Result,
UserData, UserDataFields, UserDataMethods, UserDataRef, UserDataRegistry, Value, Variadic,
UserData, UserDataFields, UserDataMethods, UserDataOwned, UserDataRef, UserDataRegistry, Value, Variadic,
};
#[test]
@@ -733,6 +733,43 @@ fn test_metatable() -> Result<()> {
Ok(())
}
#[test]
fn test_userdata_type_name() -> Result<()> {
struct MyUserData;
impl UserData for MyUserData {}
struct MyUserdataCustom;
impl UserData for MyUserdataCustom {
fn add_fields<F: UserDataFields<Self>>(fields: &mut F) {
fields.add_meta_field_with(MetaMethod::Type, |_| Ok("MyCustomName"));
}
}
// mlua always sets __name/__type; override with a non-string to test the "userdata" fallback
struct MyUserdataInvalid;
impl UserData for MyUserdataInvalid {
fn add_fields<F: UserDataFields<Self>>(fields: &mut F) {
fields.add_meta_field_with(MetaMethod::Type, |_| Ok(42_i64));
}
}
let lua = Lua::new();
// Default is the Rust type name
let ud = lua.create_userdata(MyUserData)?;
assert_eq!(ud.type_name()?, "MyUserData");
// Custom name from metatable
let ud = lua.create_userdata(MyUserdataCustom)?;
assert_eq!(ud.type_name()?, "MyCustomName");
// Invalid type name should fallback to "userdata"
let ud = lua.create_userdata(MyUserdataInvalid)?;
assert_eq!(ud.type_name()?.to_str()?, "userdata");
Ok(())
}
#[test]
fn test_userdata_proxy() -> Result<()> {
struct MyUserData(i64);
@@ -1376,7 +1413,7 @@ fn test_userdata_namecall() -> Result<()> {
struct MyUserData;
impl UserData for MyUserData {
fn register(registry: &mut mlua::UserDataRegistry<Self>) {
fn register(registry: &mut UserDataRegistry<Self>) {
registry.add_method("method", |_, _, ()| Ok("method called"));
registry.add_field_method_get("field", |_, _| Ok("field value"));
@@ -1422,3 +1459,49 @@ fn test_userdata_get_path() -> Result<()> {
Ok(())
}
#[test]
fn test_userdata_owned() -> Result<()> {
#[derive(Debug)]
struct MyUserdata(Arc<i64>);
impl UserData for MyUserdata {
fn register(registry: &mut UserDataRegistry<Self>) {
registry.add_method("num", |_, this, ()| Ok(*this.0));
}
}
let lua = Lua::new();
let rc = Arc::new(42);
// It takes ownership and destructs the Lua userdata
let ud = lua.create_userdata(MyUserdata(rc.clone()))?;
assert_eq!(Arc::strong_count(&rc), 2);
let owned: UserDataOwned<MyUserdata> = lua.convert(&ud)?;
assert_eq!(*owned.0.0, 42);
drop(owned);
assert_eq!(Arc::strong_count(&rc), 1);
match ud.borrow::<MyUserdata>() {
Err(Error::UserDataDestructed) => {}
r => panic!("expected UserDataDestructed, got {:?}", r),
}
// Cannot take while borrowed
let rc = Arc::new(7);
let ud = lua.create_userdata(MyUserdata(rc.clone()))?;
let borrowed = ud.borrow::<MyUserdata>()?;
match lua.convert::<UserDataOwned<MyUserdata>>(&ud) {
Err(Error::UserDataBorrowMutError) => {}
r => panic!("expected UserDataBorrowMutError, got {:?}", r),
}
drop(borrowed);
// Works as a function parameter
let f = lua.create_function(|_, owned: UserDataOwned<MyUserdata>| Ok(*owned.0.0))?;
let rc = Arc::new(55);
let ud = lua.create_userdata(MyUserdata(rc.clone()))?;
assert_eq!(f.call::<i64>(ud)?, 55);
assert_eq!(Arc::strong_count(&rc), 1); // dropped after call
Ok(())
}
+29 -1
View File
@@ -2,7 +2,10 @@ use std::collections::HashMap;
use std::os::raw::c_void;
use std::ptr;
use mlua::{Error, LightUserData, Lua, MultiValue, Result, UserData, UserDataMethods, Value};
use mlua::{
AnyUserData, Error, LightUserData, Lua, MultiValue, Result, UserData, UserDataMethods, UserDataRegistry,
Value,
};
#[test]
fn test_value_eq() -> Result<()> {
@@ -218,6 +221,31 @@ fn test_debug_format() -> Result<()> {
.map(Value::UserData)?;
assert!(format!("{ud:#?}").starts_with("HashMap<i32, String>:"));
struct ToDebugUserData;
impl UserData for ToDebugUserData {
fn register(registry: &mut UserDataRegistry<Self>) {
registry.add_meta_method("__tostring", |_, _, ()| Ok("regular-string"));
registry.add_meta_method("__todebugstring", |_, _, ()| Ok("debug-string"));
}
}
let debug_ud = Value::UserData(lua.create_userdata(ToDebugUserData)?);
assert_eq!(debug_ud.to_string()?, "regular-string");
assert_eq!(format!("{debug_ud:#?}"), "debug-string");
struct ToStringUserData;
impl UserData for ToStringUserData {
fn register(registry: &mut UserDataRegistry<Self>) {
registry.add_meta_method("__tostring", |_, _, ()| Ok("regular-string"));
}
}
let tostring_only_ud = Value::UserData(lua.create_userdata(ToStringUserData)?);
assert_eq!(format!("{tostring_only_ud:#?}"), "regular-string");
// Check that `AnyUsedata` pretty debug format is same as for `Value::UserData`
let any_ud: AnyUserData = lua.create_userdata(ToDebugUserData)?;
let value_ud = Value::UserData(any_ud.clone());
assert_eq!(format!("{any_ud:#?}"), format!("{value_ud:#?}"));
Ok(())
}