Compare commits

...

64 Commits

Author SHA1 Message Date
Alex Orlenko cb1ac28f2a v0.5.4 2021-04-20 02:05:05 +01:00
Alex Orlenko 3e7f25670a Don't read lua.h from build script (was not used) 2021-04-20 02:05:00 +01:00
Alex Orlenko 0d404ce4c3 Don't fail CI on coverage 2021-04-16 22:43:21 +01:00
Alex Orlenko e26cec5db9 Drop 'feature(link_args)' (removed from nightly). Don't run tests for LuaJIT 2.0.5 2021-04-16 22:27:28 +01:00
Alex Orlenko 0bd36b42e7 Make error::Error non_exhaustive 2021-04-16 22:10:10 +01:00
Alex Orlenko e0da6ac929 Hide raw_sequence_values_by_len under async/serialize feature flags 2021-04-16 22:09:49 +01:00
Alex Orlenko 0c7db4916c Serialize only known (registered) userdata.
This reverts commit 7332c6a.
Non-static userdata is a special case and can cause segfault if try to serialize it.
Now it should be safe, plus I added non-static userdata destructor to generate better error messages
in case of accessing destructed userdata.
2021-04-16 22:01:55 +01:00
Alex Orlenko b9589491e4 Improve panic handling (check for twice resumed panics) 2021-04-15 23:04:36 +01:00
Alex Orlenko 58cb371f06 Add rerun-if-changed instructions to build script 2021-04-14 22:33:23 +01:00
Alex Orlenko 8add60b019 Don't check LUA_LIB_NAME if it's not needed 2021-04-14 22:25:49 +01:00
Alex Orlenko c363fb9288 v0.5.3 2021-03-04 00:01:06 +00:00
Alex Orlenko 3900e23839 Fix compilation warnings on nightly 2021-03-03 23:36:28 +00:00
Alex Orlenko 726fde7e1f Optimise async callbacks (polling)
call async Rust callback [sum] 3 10
                        time:   [59.338 us 59.729 us 60.097 us]
                        change: [-10.336% -8.6212% -6.8003%] (p = 0.00 < 0.05)
                        Performance has improved.
2021-03-03 23:21:56 +00:00
Alex Orlenko 7cb9c4f39c Fix bug in returning nil-prefixed multi values from async function 2021-03-03 22:32:22 +00:00
Alex Orlenko b93ace0224 v0.5.2 2021-02-27 19:32:25 +00:00
Alex Orlenko 5f37bf812d Fix/whitelist some clippy warnings 2021-02-27 19:23:08 +00:00
Alex Orlenko 1f7e760d20 Add codecov coverage report 2021-02-27 18:03:53 +00:00
Alex Orlenko 90bea4aa34 Update README and keywords 2021-02-27 13:28:47 +00:00
Alex Orlenko 7775b4a99c Bump copyright year 2021-02-26 10:35:00 +00:00
Alex Orlenko 1d9cda10eb Add ToLua implementation for Cow<str> and Cow<CStr> 2021-02-26 10:23:36 +00:00
Alex Orlenko 7332c6a28c Remove registered_userdata_mt check 2021-02-22 20:38:36 +00:00
Alex Orlenko 94670e3fdb rustfmt 2021-02-22 20:13:56 +00:00
Alex Orlenko 335f433df4 Optimize callbacks 2021-02-21 23:52:20 +00:00
Alex Orlenko 2aed548747 Fix scoped async destruction of partially polled futures 2021-02-21 23:52:07 +00:00
Alex Orlenko 6a77b5f003 Update benchmarks:
- Refactor
- Add async benchmarks
2021-02-21 18:48:45 +00:00
Alex Orlenko aeb66115f7 v0.5.1 2021-01-20 11:04:03 +00:00
Alex Orlenko ce873a40bf Update CHANGELOG 2021-01-20 11:00:47 +00:00
Alex Orlenko 8de75d1c18 Update tokio to 1.0 for async examples 2021-01-20 10:47:27 +00:00
Alex Orlenko b6ff501b8c Fix numeric types conversion for 32bit lua. Fix #27 2021-01-20 10:46:23 +00:00
Alex Orlenko 0e73ae18f4 Update CI 2021-01-16 13:32:38 +00:00
Alex Orlenko e62fd400d7 Remove unused exports from glue.{c,rs} && Fix some clippy warnings 2021-01-16 13:31:45 +00:00
Alex Orlenko 1c79f646de Update README 2021-01-16 13:31:34 +00:00
Alex Orlenko 7f5fd36a2b Merge pull request #26 from wez/cross
Support cross compilation
2021-01-14 15:35:35 +00:00
Wez Furlong faf19e4a06 Allow luajit to build in the pointer size cross compilation case 2021-01-13 20:01:12 -08:00
Wez Furlong 24d9099ef7 install more bits for cross compilation jobs 2021-01-13 10:38:08 -08:00
Wez Furlong 84003f31e7 Add CI for cross compilation cases 2021-01-13 10:18:53 -08:00
Wez Furlong e0d9ec41e2 Support cross compilation
This commit teaches the build script to recognize when it is
cross-compiling and switch to an alternative approach for generating
the `glue.rs` module.

It defaults to the equivalent logic found in the lua headers to
set the default types and parameters.

Notably: it doesn't statically produce the default lua paths as we
cannot know these without either executing the code (not guaranteed
possible when cross compiling) or regexing out the paths from the
headers (a bit brittle).  An alternative approach might be to use
something like `lazy_static` to ask the library for its compiled in
values once at runtime.

I've tested this with:

```
cargo build --target armv7-unknown-linux-gnueabihf --features lua51,vendored
cargo build --target armv7-unknown-linux-gnueabihf --features lua52,vendored
cargo build --target armv7-unknown-linux-gnueabihf --features lua53,vendored
cargo build --target armv7-unknown-linux-gnueabihf --features lua54,vendored
cargo build --target armv7-unknown-linux-gnueabihf --features luajit,vendored
```

All except luajit compile.  Luajit itself doesn't cross compile, so I
don't think we can ever reasonably get that to work.

I haven't tried to run any of this yet; my use case is actually for mac
(https://github.com/wez/wezterm/pull/426) so I need to commit this and
try patching it in over there before I can see if that truly worked
end-to-end.

refs: https://github.com/khvzak/mlua/issues/14
2021-01-13 09:55:53 -08:00
Alex Orlenko 618874ef3c v0.5.0 2020-12-31 13:39:42 +00:00
Alex Orlenko 9f82cbe0c5 Update documentation 2020-12-31 13:39:38 +00:00
Alex Orlenko 1a81f8d447 Update compile tests 2020-12-29 23:20:06 +00:00
Alex Orlenko bedd430eb5 Re-export mlua_derive 2020-12-29 22:26:15 +00:00
Alex Orlenko afc41ab23c Add serialization example & update others 2020-12-29 21:39:34 +00:00
Alex Orlenko c5d0ccc433 Add reqwest http client example to fetch json 2020-12-28 15:21:45 +00:00
Alex Orlenko ce8955f5b9 Don't try to disable c modules without package loaded. Fix #24 2020-12-22 23:05:08 +00:00
Alex Orlenko 368c4428eb Wider lua-src and luajit-src versions 2020-12-14 00:51:35 +00:00
Alex Orlenko 6e2b687cb7 Serde support (serialize feature flag) 2020-12-14 00:51:26 +00:00
Alex Orlenko 661f8e592a Update num-traits dependency
Fix f32-to-f64 conversion tests
2020-11-04 21:22:24 +00:00
Alex Orlenko c8a7aa947c Update compile tests (temporary set TRYBUILD=overwrite) 2020-10-27 23:39:52 +00:00
Alex Orlenko a52135b62d Update compile tests error messages 2020-09-28 23:24:53 +01:00
Alex Orlenko 68378a3957 Merge pull request #15 from kaikalii/master
impl `ToLua` and `FromLua` for `HashSet` and `BTreeSet`
2020-09-28 23:14:17 +01:00
Kai Schmidt c65058a40a impl ToLua and FromLua for HashSet and BTreeSet 2020-09-27 17:54:47 -07:00
Alex Orlenko 93d63cef35 v0.4.2 2020-08-17 12:17:08 +01:00
Alex Orlenko b743245aba Update CHANGELOG 2020-08-17 11:14:40 +01:00
Alex Orlenko a172c021c9 Update set_memory_limit doc 2020-07-30 12:16:25 +01:00
Alex Orlenko de2c5cd9a9 Fix compilation warnings on nightly rust 2020-07-28 21:10:55 +01:00
Alex Orlenko d201beadc9 Add ChunkMode enum to mark chunks as text or binary 2020-07-28 21:04:21 +01:00
Alex Orlenko dd58cdad52 Add Function::dump() to dump lua function to a binary chunk 2020-07-27 23:26:33 +01:00
Alex Orlenko 5c8a5e0a5a Merge pull request #9 from HybridEidolon/bytecode-chunks
Make Lua::load load binary chunks when unsafe
2020-07-27 14:19:33 +01:00
Alex Orlenko e07c53eafe Update compile tests (2) 2020-07-27 13:51:21 +01:00
Alex Orlenko ad619390e1 Run compile tests on macos (was ubuntu-18.04) 2020-07-27 11:34:48 +01:00
Alex Orlenko 350602ab6e Update lua-src dependency to 5.4.0 2020-07-27 10:52:28 +01:00
Alex Orlenko 4b1bc88273 Update compile tests 2020-07-27 10:49:01 +01:00
Eidolon 883bf082b9 Make Lua::load load binary chunks when unsafe 2020-07-27 03:37:38 -05:00
Alex Orlenko 4265250cfd 0.4.1 release
Fix docs.rs build features
Update Cargo.toml description
2020-06-08 14:08:07 +01:00
66 changed files with 3118 additions and 760 deletions
+23
View File
@@ -0,0 +1,23 @@
name: coverage
on: [push]
jobs:
test:
name: coverage
runs-on: ubuntu-latest
container:
image: xd009642/tarpaulin
options: --security-opt seccomp=unconfined
steps:
- name: Checkout repository
uses: actions/checkout@v2
- name: Generate code coverage
run: |
cargo tarpaulin --verbose --features lua53,vendored,async,send,serialize --out xml --exclude-files benches --exclude-files tests --exclude-files build --exclude-files src/ffi
- name: Upload to codecov.io
uses: codecov/codecov-action@v1
with:
token: ${{secrets.CODECOV_TOKEN}}
fail_ci_if_error: false
+76 -40
View File
@@ -27,15 +27,78 @@ jobs:
- name: Build ${{ matrix.lua }} vendored
run: |
cargo build --release --features "${{ matrix.lua }} vendored"
cargo build --release --features "${{ matrix.lua }} vendored async send"
cargo build --release --features "${{ matrix.lua }} vendored async send serialize"
shell: bash
- name: Build ${{ matrix.lua }} pkg-config
if: ${{ matrix.os == 'ubuntu-18.04' && matrix.lua != 'lua54' }}
run: |
sudo apt-get update -y
sudo apt-get update
sudo apt-get install -y --no-install-recommends liblua5.3-dev liblua5.2-dev liblua5.1-0-dev libluajit-5.1-dev
cargo build --release --features "${{ matrix.lua }}"
build_aarch64_cross_macos:
name: Cross-compile to aarch64-apple-darwin
runs-on: macos-11.0
needs: build
strategy:
matrix:
lua: [lua54, lua53, lua52, lua51, luajit]
steps:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with:
toolchain: nightly
target: aarch64-apple-darwin
override: true
- name: Cross-compile
run: cargo build --target aarch64-apple-darwin --features "${{ matrix.lua }} async send serialize vendored"
build_aarch64_cross_ubuntu:
name: Cross-compile to aarch64-unknown-linux-gnu
runs-on: ubuntu-18.04
needs: build
strategy:
matrix:
lua: [lua54, lua53, lua52, lua51, luajit]
steps:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with:
toolchain: nightly
target: aarch64-unknown-linux-gnu
override: true
- name: Install ARM compiler toolchain
run: |
sudo apt-get update
sudo apt-get install -y --no-install-recommends gcc-aarch64-linux-gnu libc6-dev-arm64-cross
shell: bash
- name: Cross-compile
run: cargo build --target aarch64-unknown-linux-gnu --features "${{ matrix.lua }} async send serialize vendored"
shell: bash
build_armv7_cross_ubuntu:
name: Cross-compile to armv7-unknown-linux-gnueabihf
runs-on: ubuntu-18.04
needs: build
strategy:
matrix:
lua: [lua54, lua53, lua52, lua51]
steps:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with:
toolchain: nightly
target: armv7-unknown-linux-gnueabihf
override: true
- name: Install ARM compiler toolchain
run: |
sudo apt-get update
sudo apt-get install -y --no-install-recommends gcc-arm-linux-gnueabihf libc-dev-armhf-cross
shell: bash
- name: Cross-compile
run: cargo build --target armv7-unknown-linux-gnueabihf --features "${{ matrix.lua }} async send serialize vendored"
shell: bash
test:
name: Test
runs-on: ${{ matrix.os }}
@@ -60,41 +123,19 @@ jobs:
target: ${{ matrix.target }}
override: true
- name: Run ${{ matrix.lua }} tests
if: ${{ matrix.os != 'macos-latest' || matrix.lua != 'luajit' }}
run: |
cargo test --release --features "${{ matrix.lua }} vendored"
cargo test --release --features "${{ matrix.lua }} vendored async send"
cargo test --release --features "${{ matrix.lua }} vendored async send serialize"
shell: bash
- name: Run compile tests
if: ${{ matrix.os == 'ubuntu-18.04' && matrix.lua == 'lua53' }}
- name: Run compile tests (macos lua53)
if: ${{ matrix.os == 'macos-latest' && matrix.lua == 'lua53' }}
run: |
cargo test --release --features "${{ matrix.lua }} vendored" -- --ignored
cargo test --release --features "${{ matrix.lua }} vendored async send" -- --ignored
shell: bash
test_luajit_macos:
name: Test LuaJIT on macOS
runs-on: macos-latest
needs: build
steps:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with:
toolchain: nightly
target: x86_64-apple-darwin
override: true
- name: Run LuaJIT 2.0.5 tests
run: |
brew install luajit pkg-config
cargo test --tests --release --features "luajit async send" -- --test-threads=1
shell: bash
- name: Run LuaJIT vendored tests
run: |
cargo test --release --features "luajit vendored async send"
TRYBUILD=overwrite cargo test --release --features "${{ matrix.lua }} vendored" -- --ignored
TRYBUILD=overwrite cargo test --release --features "${{ matrix.lua }} vendored async send serialize" -- --ignored
shell: bash
test_modules:
name: Test modules on Linux and macOS
name: Test modules
runs-on: ${{ matrix.os }}
needs: build
strategy:
@@ -126,21 +167,16 @@ jobs:
needs: build
strategy:
matrix:
lua: [lua53, lua51, luajit]
lua: [lua53, luajit]
defaults:
run:
shell: bash.exe --login -eo pipefail "{0}"
env:
MSYSTEM: MINGW64
CHERE_INVOKING: 1
shell: msys2 {0}
steps:
- uses: msys2/setup-msys2@v2
- uses: actions/checkout@v2
- name: Set up shell
run: echo ::add-path::C:\msys64\usr\bin\
shell: pwsh
- name: Install Rust & Lua
run: |
pacman -S --noconfirm mingw-w64-x86_64-rust mingw-w64-x86_64-lua mingw-w64-x86_64-lua51 mingw-w64-x86_64-luajit
pacman -S --noconfirm mingw-w64-x86_64-rust mingw-w64-x86_64-lua mingw-w64-x86_64-luajit mingw-w64-x86_64-pkg-config
- name: Run ${{ matrix.lua }} module tests
run: |
(cd examples/module && cargo build --release --features "${{ matrix.lua }}")
@@ -174,4 +210,4 @@ jobs:
- uses: actions-rs/clippy-check@v1
with:
token: ${{ secrets.GITHUB_TOKEN }}
args: --features "${{ matrix.lua }},async,send,vendored
args: --features "${{ matrix.lua }},vendored,async,send,serialize
+36
View File
@@ -1,3 +1,39 @@
## v0.5.4
- Build script improvements
- Improvements in panic handling (resume panic on value popping)
- Fixed bug serializing 3rd party userdata (causes segfault)
- Make error::Error non exhaustive
## v0.5.3
- Fixed bug when returning nil-prefixed multi values from async function (+ test)
- Performance optimisation for async callbacks (polling)
## v0.5.2
- Some performance optimisations (callbacks)
- `ToLua` implementation for `Cow<str>` and `Cow<CStr>`
- Fixed bug with `Scope` destruction of partially polled futures
## v0.5.1
- Support cross compilation that should work well for vendored builds (including LuaJIT with some restrictions)
- Fix numeric types conversion for 32bit Lua
- Update tokio to 1.0 for async examples
## v0.5.0
- Serde support under `serialize` feature flag.
- Re-export `mlua_derive`.
- impl `ToLua` and `FromLua` for `HashSet` and `BTreeSet`
## v0.4.2
- Added `Function::dump()` to dump lua function to a binary chunk
- Added `ChunkMode` enum to mark chunks as text or binary
- Updated `set_memory_limit` doc
## v0.4.0
- Lua 5.4 support with `MetaMethod::Close`.
+28 -16
View File
@@ -1,27 +1,24 @@
[package]
name = "mlua"
version = "0.4.0"
version = "0.5.4" # remember to update html_root_url and mlua_derive
authors = ["Aleksandr Orlenko <zxteam@pm.me>", "kyren <catherine@chucklefish.org>"]
edition = "2018"
repository = "https://github.com/khvzak/mlua"
documentation = "https://docs.rs/mlua"
readme = "README.md"
keywords = ["lua", "luajit", "async", "futures"]
keywords = ["lua", "luajit", "async", "futures", "scripting"]
categories = ["api-bindings", "asynchronous"]
license = "MIT"
links = "lua"
build = "build/main.rs"
description = """
High level bindings to Lua 5.1/5.2/5.3/5.4 (including LuaJIT)
High level bindings to Lua 5.4/5.3/5.2/5.1 (including LuaJIT)
with async/await features and support of writing native lua modules in Rust.
"""
[badges]
# github-actions = { repository = "khvzak/mlua", workflow = "CI" }
maintenance = { status = "actively-developed" }
[package.metadata.docs.rs]
features = ["async"]
features = ["lua53", "async", "send", "serialize"]
rustdoc-args = ["--cfg", "docsrs"]
[workspace]
members = [
@@ -37,41 +34,52 @@ lua52 = []
lua51 = []
luajit = []
vendored = ["lua-src", "luajit-src"]
module = []
module = ["mlua_derive"]
async = ["futures-core", "futures-task", "futures-util"]
send = []
serialize = ["serde", "erased-serde"]
[dependencies]
mlua_derive = { version = "0.5", optional = true, path = "mlua_derive" }
bstr = { version = "0.2", features = ["std"], default_features = false }
lazy_static = { version = "1.4" }
num-traits = { version = "0.2.11" }
num-traits = { version = "0.2.14" }
futures-core = { version = "0.3.5", optional = true }
futures-task = { version = "0.3.5", optional = true }
futures-util = { version = "0.3.5", optional = true }
serde = { version = "1.0", optional = true }
erased-serde = { version = "0.3", optional = true }
[build-dependencies]
cc = { version = "1.0" }
pkg-config = { version = "0.3.17" }
lua-src = { version = "535.0.4", optional = true }
luajit-src = { version = "210.1.0", optional = true }
lua-src = { version = ">= 540.0.0, < 550.0.0", optional = true }
luajit-src = { version = ">= 210.1.2, < 220.0.0", optional = true }
[dev-dependencies]
rustyline = "6.0"
criterion = "0.3"
rustyline = "7.0"
criterion = { version = "0.3.4", features = ["html_reports", "async_tokio"] }
trybuild = "1.0"
futures = "0.3.5"
hyper = "0.13"
tokio = { version = "0.2", features = ["full"] }
hyper = { version = "0.14", features = ["client", "server"] }
reqwest = { version = "0.11", features = ["json"] }
tokio = { version = "1.0", features = ["full"] }
futures-timer = "3.0"
serde_json = "1.0"
[[bench]]
name = "benchmark"
harness = false
required-features = ["async"]
[[example]]
name = "async_http_client"
required-features = ["async"]
[[example]]
name = "async_http_reqwest"
required-features = ["async", "serialize"]
[[example]]
name = "async_http_server"
required-features = ["async", "send"]
@@ -79,3 +87,7 @@ required-features = ["async", "send"]
[[example]]
name = "async_tcp_server"
required-features = ["async"]
[[example]]
name = "serialize"
required-features = ["serialize"]
+1 -1
View File
@@ -3,7 +3,7 @@ below:
MIT License
Copyright (c) 2019 A. Orlenko
Copyright (c) 2019-2021 A. Orlenko
Copyright (c) 2017 rlua
Permission is hereby granted, free of charge, to any person obtaining a copy
+59 -18
View File
@@ -1,5 +1,5 @@
# mlua
[![Build Status]][github-actions] [![Latest Version]][crates.io] [![API Documentation]][docs.rs]
[![Build Status]][github-actions] [![Latest Version]][crates.io] [![API Documentation]][docs.rs] [![Coverage Status]][codecov.io]
[Build Status]: https://github.com/khvzak/mlua/workflows/CI/badge.svg
[github-actions]: https://github.com/khvzak/mlua/actions
@@ -7,35 +7,70 @@
[crates.io]: https://crates.io/crates/mlua
[API Documentation]: https://docs.rs/mlua/badge.svg
[docs.rs]: https://docs.rs/mlua
[Coverage Status]: https://codecov.io/gh/khvzak/mlua/branch/master/graph/badge.svg?token=99339FS1CG
[codecov.io]: https://codecov.io/gh/khvzak/mlua
[Guided Tour](examples/guided_tour.rs)
`mlua` is bindings to [Lua](https://www.lua.org) programming language for Rust with a goal to provide
_safe_ (as far as it's possible), high level, easy to use, practical and flexible API.
Started as [rlua v0.15](https://github.com/amethyst/rlua/tree/0.15.3) fork, `mlua` supports *__all__* major Lua versions (including LuaJIT) and allows to write native Lua modules in Rust as well as use Lua in a standalone mode.
Started as [rlua](https://github.com/amethyst/rlua/tree/0.15.3) fork, `mlua` supports Lua 5.4, 5.3, 5.2 and 5.1 including LuaJIT (2.0.5 and 2.1 beta) and allows to write native Lua modules in Rust as well as use Lua in a standalone mode.
`mlua` supports the following Lua versions (and tested on Windows/macOS/Linux):
- Lua 5.4 (`feature = "lua54"`)
- Lua 5.3 (`feature = "lua53"`)
- Lua 5.2 (`feature = "lua52"`)
- Lua 5.1 (`feature = "lua51"`)
- LuaJIT 2.1.0 beta (`feature = "luajit"`)
- LuaJIT 2.0.5 stable (`feature = "luajit"`)
`mlua` tested on Windows/macOS/Linux including module mode in [GitHub Actions] on `x86_64` platform and cross-compilation to `aarch64` (other targes are also supported).
Additional `feature = "vendored"` enables building static Lua from sources during `mlua` compilation.
[GitHub Actions]: https://github.com/khvzak/mlua/actions
## Usage
### Feature flags
`mlua` uses feature flags to reduce the amount of depenendies, compiled code and allow to choose only required set of features.
Below is a list of the available feature flags. By default `mlua` does not enable any features.
* `lua54`: activate Lua [5.4] support
* `lua53`: activate Lua [5.3] support
* `lua52`: activate Lua [5.2] support
* `lua51`: activate Lua [5.1] support
* `luajit`: activate [LuaJIT] support
* `vendored`: build static Lua(JIT) library from sources during `mlua` compilation using [lua-src] or [luajit-src] crates
* `module`: enable module mode (building loadable `cdylib` library for Lua)
* `async`: enable async/await support (any executor can be used, eg. [tokio] or [async-std])
* `send`: make `mlua::Lua` transferable across thread boundaries (adds [`Send`] requirement to `mlua::Function` and `mlua::UserData`)
* `serialize`: add serialization and deserialization support to `mlua` types usign [serde] framework
[5.4]: https://www.lua.org/manual/5.4/manual.html
[5.3]: https://www.lua.org/manual/5.3/manual.html
[5.2]: https://www.lua.org/manual/5.2/manual.html
[5.1]: https://www.lua.org/manual/5.1/manual.html
[LuaJIT]: https://luajit.org/
[lua-src]: https://github.com/khvzak/lua-src-rs
[luajit-src]: https://github.com/khvzak/luajit-src-rs
[tokio]: https://github.com/tokio-rs/tokio
[async-std]: https://github.com/async-rs/async-std
[`Send`]: https://doc.rust-lang.org/std/marker/trait.Send.html
[serde]: https://github.com/serde-rs/serde
### Async/await support
Starting from v0.3, `mlua` supports async/await for all Lua versions. This works using Lua [coroutines](https://www.lua.org/manual/5.3/manual.html#2.6) and require running [Thread](https://docs.rs/mlua/latest/mlua/struct.Thread.html) along with enabling `feature = "async"` in `Cargo.toml`.
`mlua` supports async/await for all Lua versions. This works using Lua [coroutines](https://www.lua.org/manual/5.3/manual.html#2.6) and require running [Thread](https://docs.rs/mlua/latest/mlua/struct.Thread.html) along with enabling `feature = "async"` in `Cargo.toml`.
**Examples**:
- [HTTP Client](examples/async_http_client.rs)
- [HTTP Client (json)](examples/async_http_reqwest.rs)
- [HTTP Server](examples/async_http_server.rs)
- [TCP Server](examples/async_tcp_server.rs)
### Serialization (serde) support
With `serialize` feature flag enabled, `mlua` allows you to serialize/deserialize any type that implements [`serde::Serialize`] and [`serde::Deserialize`] into/from [`mlua::Value`]. In addition `mlua` provides [`serde::Serialize`] trait implementation for it (including `UserData` support).
[Example](examples/serialize.rs)
[`serde::Serialize`]: https://docs.serde.rs/serde/ser/trait.Serialize.html
[`serde::Deserialize`]: https://docs.serde.rs/serde/de/trait.Deserialize.html
[`mlua::Value`]: https://docs.rs/mlua/latest/mlua/enum.Value.html
### Compiling
You have to enable one of the features `lua54`, `lua53`, `lua52`, `lua51` or `luajit`, according to the choosen Lua version.
@@ -55,11 +90,13 @@ my_project $ LUA_INC=$HOME/tmp/lua-5.2.4/src LUA_LIB=$HOME/tmp/lua-5.2.4/src LUA
Just enable the `vendored` feature and cargo will automatically build and link specified lua/luajit version. This is the easiest way to get started with `mlua`.
### Standalone mode
In a standalone mode `mlua` allows to add to your application scripting support with a gently configured Lua runtime to ensure safety and soundness.
Add to `Cargo.toml` :
``` toml
[dependencies]
mlua = { version = "0.4", features = ["lua53"] }
mlua = { version = "0.5", features = ["lua53", "vendored"] }
```
`main.rs`
@@ -83,6 +120,7 @@ fn main() -> LuaResult<()> {
```
### Module mode
In a module mode `mlua` allows to create a compiled Lua module that can be loaded from Lua code using [`require`](https://www.lua.org/manual/5.3/manual.html#pdf-require). In this case `mlua` uses an external Lua runtime which could lead to potential unsafety due to unpredictability of the Lua environment and usage of libraries such as [`debug`](https://www.lua.org/manual/5.3/manual.html#6.10).
[Example](examples/module)
@@ -93,15 +131,12 @@ Add to `Cargo.toml` :
crate-type = ["cdylib"]
[dependencies]
mlua = { version = "0.4", features = ["lua53", "module"] }
mlua_derive = "0.4"
mlua = { version = "0.5", features = ["lua53", "vendored", "module"] }
```
`lib.rs` :
``` rust
#[macro_use]
extern crate mlua_derive;
use mlua::prelude::*;
fn hello(_: &Lua, name: String) -> LuaResult<()> {
@@ -109,7 +144,7 @@ fn hello(_: &Lua, name: String) -> LuaResult<()> {
Ok(())
}
#[lua_module]
#[mlua::lua_module]
fn my_module(lua: &Lua) -> LuaResult<LuaTable> {
let exports = lua.create_table()?;
exports.set("hello", lua.create_function(hello)?)?;
@@ -133,11 +168,17 @@ rustflags = [
"-C", "link-arg=-undefined",
"-C", "link-arg=dynamic_lookup",
]
[target.aarch64-apple-darwin]
rustflags = [
"-C", "link-arg=-undefined",
"-C", "link-arg=dynamic_lookup",
]
```
On Linux you can build modules normally with `cargo build --release`.
Vendored and non-vendored builds are supported for these OS.
On Windows `vendored` mode is not supported since you need to link to a Lua dll.
On Windows `vendored` mode for modules is not supported since you need to link to a Lua dll.
Easiest way is to use either MinGW64 (as part of [MSYS2](https://github.com/msys2/msys2) package) with `pkg-config` or
MSVC with `LUA_INC` / `LUA_LIB` / `LUA_LIB_NAME` environment variables.
+171 -110
View File
@@ -1,22 +1,22 @@
#![cfg_attr(
all(feature = "luajit", target_os = "macos", target_arch = "x86_64"),
feature(link_args)
)]
#[cfg_attr(
all(feature = "luajit", target_os = "macos", target_arch = "x86_64"),
link_args = "-pagezero_size 10000 -image_base 100000000"
)]
extern "system" {}
use criterion::{criterion_group, criterion_main, BatchSize, Criterion};
use std::time::Duration;
use tokio::runtime::Runtime;
use tokio::task;
use mlua::prelude::*;
fn collect_gc_twice(lua: &Lua) {
lua.gc_collect().unwrap();
lua.gc_collect().unwrap();
}
fn create_table(c: &mut Criterion) {
c.bench_function("create table", |b| {
b.iter_batched_ref(
|| Lua::new(),
|lua| {
let lua = Lua::new();
c.bench_function("create [table empty]", |b| {
b.iter_batched(
|| collect_gc_twice(&lua),
|_| {
lua.create_table().unwrap();
},
BatchSize::SmallInput,
@@ -25,12 +25,14 @@ fn create_table(c: &mut Criterion) {
}
fn create_array(c: &mut Criterion) {
c.bench_function("create array 10", |b| {
b.iter_batched_ref(
|| Lua::new(),
|lua| {
let lua = Lua::new();
c.bench_function("create [array] 10", |b| {
b.iter_batched(
|| collect_gc_twice(&lua),
|_| {
let table = lua.create_table().unwrap();
for i in 1..11 {
for i in 1..=10 {
table.set(i, i).unwrap();
}
},
@@ -40,10 +42,12 @@ fn create_array(c: &mut Criterion) {
}
fn create_string_table(c: &mut Criterion) {
c.bench_function("create string table 10", |b| {
b.iter_batched_ref(
|| Lua::new(),
|lua| {
let lua = Lua::new();
c.bench_function("create [table string] 10", |b| {
b.iter_batched(
|| collect_gc_twice(&lua),
|_| {
let table = lua.create_table().unwrap();
for &s in &["1", "2", "3", "4", "5", "6", "7", "8", "9", "10"] {
let s = lua.create_string(s).unwrap();
@@ -55,30 +59,20 @@ fn create_string_table(c: &mut Criterion) {
});
}
fn call_add_function(c: &mut Criterion) {
c.bench_function("call add function 3 10", |b| {
fn call_lua_function(c: &mut Criterion) {
let lua = Lua::new();
c.bench_function("call Lua function [sum] 3 10", |b| {
b.iter_batched_ref(
|| {
let lua = Lua::new();
let f = {
let f: LuaFunction = lua
.load(
r#"
function(a, b, c)
return a + b + c
end
"#,
)
.eval()
.unwrap();
lua.create_registry_value(f).unwrap()
};
(lua, f)
collect_gc_twice(&lua);
lua.load("function(a, b, c) return a + b + c end")
.eval::<LuaFunction>()
.unwrap()
},
|(lua, f)| {
let add_function: LuaFunction = lua.registry_value(f).unwrap();
|function| {
for i in 0..10 {
let _result: i64 = add_function.call((i, i + 1, i + 2)).unwrap();
let _result: i64 = function.call((i, i + 1, i + 2)).unwrap();
}
},
BatchSize::SmallInput,
@@ -86,72 +80,75 @@ fn call_add_function(c: &mut Criterion) {
});
}
fn call_add_callback(c: &mut Criterion) {
c.bench_function("call callback add 2 10", |b| {
fn call_sum_callback(c: &mut Criterion) {
let lua = Lua::new();
let callback = lua
.create_function(|_, (a, b, c): (i64, i64, i64)| Ok(a + b + c))
.unwrap();
lua.globals().set("callback", callback).unwrap();
c.bench_function("call Rust callback [sum] 3 10", |b| {
b.iter_batched_ref(
|| {
let lua = Lua::new();
let f = {
let c: LuaFunction = lua
.create_function(|_, (a, b, c): (i64, i64, i64)| Ok(a + b + c))
.unwrap();
lua.globals().set("callback", c).unwrap();
let f: LuaFunction = lua
.load(
r#"
function()
for i = 1,10 do
callback(i, i, i)
end
end
"#,
)
.eval()
.unwrap();
lua.create_registry_value(f).unwrap()
};
(lua, f)
collect_gc_twice(&lua);
lua.load("function() for i = 1,10 do callback(i, i+1, i+2) end end")
.eval::<LuaFunction>()
.unwrap()
},
|(lua, f)| {
let entry_function: LuaFunction = lua.registry_value(f).unwrap();
entry_function.call::<_, ()>(()).unwrap();
|function| {
function.call::<_, ()>(()).unwrap();
},
BatchSize::SmallInput,
);
});
}
fn call_append_callback(c: &mut Criterion) {
c.bench_function("call callback append 10", |b| {
fn call_async_sum_callback(c: &mut Criterion) {
let lua = Lua::new();
let callback = lua
.create_async_function(|_, (a, b, c): (i64, i64, i64)| async move {
task::yield_now().await;
Ok(a + b + c)
})
.unwrap();
lua.globals().set("callback", callback).unwrap();
c.bench_function("call async Rust callback [sum] 3 10", |b| {
let rt = Runtime::new().unwrap();
b.to_async(rt).iter_batched(
|| {
collect_gc_twice(&lua);
lua.load("function() for i = 1,10 do callback(i, i+1, i+2) end end")
.eval::<LuaFunction>()
.unwrap()
},
|function| async move {
function.call_async::<_, ()>(()).await.unwrap();
},
BatchSize::SmallInput,
);
});
}
fn call_concat_callback(c: &mut Criterion) {
let lua = Lua::new();
let callback = lua
.create_function(|_, (a, b): (LuaString, LuaString)| {
Ok(format!("{}{}", a.to_str()?, b.to_str()?))
})
.unwrap();
lua.globals().set("callback", callback).unwrap();
c.bench_function("call Rust callback [concat string] 10", |b| {
b.iter_batched_ref(
|| {
let lua = Lua::new();
let f = {
let c: LuaFunction = lua
.create_function(|_, (a, b): (LuaString, LuaString)| {
Ok(format!("{}{}", a.to_str()?, b.to_str()?))
})
.unwrap();
lua.globals().set("callback", c).unwrap();
let f: LuaFunction = lua
.load(
r#"
function()
for _ = 1,10 do
callback("a", "b")
end
end
"#,
)
.eval()
.unwrap();
lua.create_registry_value(f).unwrap()
};
(lua, f)
collect_gc_twice(&lua);
lua.load("function() for i = 1,10 do callback('a', tostring(i)) end end")
.eval::<LuaFunction>()
.unwrap()
},
|(lua, f)| {
let entry_function: LuaFunction = lua.registry_value(f).unwrap();
entry_function.call::<_, ()>(()).unwrap();
|function| {
function.call::<_, ()>(()).unwrap();
},
BatchSize::SmallInput,
);
@@ -159,10 +156,12 @@ fn call_append_callback(c: &mut Criterion) {
}
fn create_registry_values(c: &mut Criterion) {
c.bench_function("create registry 10", |b| {
b.iter_batched_ref(
|| Lua::new(),
|lua| {
let lua = Lua::new();
c.bench_function("create [registry value] 10", |b| {
b.iter_batched(
|| collect_gc_twice(&lua),
|_| {
for _ in 0..10 {
lua.create_registry_value(lua.pack(true).unwrap()).unwrap();
}
@@ -177,10 +176,12 @@ fn create_userdata(c: &mut Criterion) {
struct UserData(i64);
impl LuaUserData for UserData {}
c.bench_function("create userdata 10", |b| {
b.iter_batched_ref(
|| Lua::new(),
|lua| {
let lua = Lua::new();
c.bench_function("create [table userdata] 10", |b| {
b.iter_batched(
|| collect_gc_twice(&lua),
|_| {
let table: LuaTable = lua.create_table().unwrap();
for i in 1..11 {
table.set(i, UserData(i)).unwrap();
@@ -191,20 +192,80 @@ fn create_userdata(c: &mut Criterion) {
});
}
fn call_userdata_method(c: &mut Criterion) {
struct UserData(i64);
impl LuaUserData for UserData {
fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
methods.add_method("method", |_, this, ()| Ok(this.0));
}
}
let lua = Lua::new();
lua.globals().set("userdata", UserData(10)).unwrap();
c.bench_function("call [userdata method] 10", |b| {
b.iter_batched_ref(
|| {
collect_gc_twice(&lua);
lua.load("function() for i = 1,10 do userdata:method() end end")
.eval::<LuaFunction>()
.unwrap()
},
|function| {
function.call::<_, ()>(()).unwrap();
},
BatchSize::SmallInput,
);
});
}
fn call_async_userdata_method(c: &mut Criterion) {
#[derive(Clone, Copy)]
struct UserData(i64);
impl LuaUserData for UserData {
fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
methods.add_async_method("method", |_, this, ()| async move { Ok(this.0) });
}
}
let lua = Lua::new();
lua.globals().set("userdata", UserData(10)).unwrap();
c.bench_function("call async [userdata method] 10", |b| {
let rt = Runtime::new().unwrap();
b.to_async(rt).iter_batched(
|| {
collect_gc_twice(&lua);
lua.load("function() for i = 1,10 do userdata:method() end end")
.eval::<LuaFunction>()
.unwrap()
},
|function| async move {
function.call_async::<_, ()>(()).await.unwrap();
},
BatchSize::SmallInput,
);
});
}
criterion_group! {
name = benches;
config = Criterion::default()
.sample_size(200)
.sample_size(300)
.measurement_time(Duration::from_secs(10))
.noise_threshold(0.02);
targets =
create_table,
create_array,
create_string_table,
call_add_function,
call_add_callback,
call_append_callback,
call_lua_function,
call_sum_callback,
call_async_sum_callback,
call_concat_callback,
create_registry_values,
create_userdata,
call_userdata_method,
call_async_userdata_method,
}
criterion_main!(benches);
+27 -46
View File
@@ -1,13 +1,19 @@
use std::env;
use std::fs::File;
use std::io::{BufRead, BufReader, Result};
use std::ops::Bound;
use std::path::{Path, PathBuf};
use std::path::PathBuf;
fn get_env_var(name: &str) -> String {
match env::var(name) {
Ok(val) => val,
Err(env::VarError::NotPresent) => String::new(),
Err(err) => panic!("cannot get {}: {}", name, err),
}
}
pub fn probe_lua() -> PathBuf {
let include_dir = env::var_os("LUA_INC").unwrap_or_default();
let lib_dir = env::var_os("LUA_LIB").unwrap_or_default();
let lua_lib = env::var_os("LUA_LIB_NAME").unwrap_or_default();
let include_dir = get_env_var("LUA_INC");
let lib_dir = get_env_var("LUA_LIB");
let lua_lib = get_env_var("LUA_LIB_NAME");
println!("cargo:rerun-if-env-changed=LUA_INC");
println!("cargo:rerun-if-env-changed=LUA_LIB");
@@ -16,11 +22,22 @@ pub fn probe_lua() -> PathBuf {
let need_lua_lib = cfg!(any(not(feature = "module"), target_os = "windows"));
if include_dir != "" && (!need_lua_lib || lib_dir != "") {
if lua_lib == "" {
panic!("LUA_LIB_NAME is not set");
if include_dir != "" {
if need_lua_lib {
if lib_dir == "" {
panic!("LUA_LIB is not set");
}
if lua_lib == "" {
panic!("LUA_LIB_NAME is not set");
}
let mut link_lib = "";
if get_env_var("LUA_LINK") == "static" {
link_lib = "static=";
};
println!("cargo:rustc-link-search=native={}", lib_dir);
println!("cargo:rustc-link-lib={}{}", link_lib, lua_lib);
}
let _version = use_custom_lua(&include_dir, &lib_dir, &lua_lib).unwrap();
return PathBuf::from(include_dir);
}
@@ -100,39 +117,3 @@ pub fn probe_lua() -> PathBuf {
lua.unwrap().include_paths[0].clone()
}
}
fn use_custom_lua<S: AsRef<Path>>(include_dir: &S, lib_dir: &S, lua_lib: &S) -> Result<String> {
let mut version_found = String::new();
// Find LUA_VERSION_NUM
let mut lua_h_path = include_dir.as_ref().to_owned();
lua_h_path.push("lua.h");
let f = File::open(lua_h_path)?;
let reader = BufReader::new(f);
for line in reader.lines() {
let line = line?;
let parts = line.split_whitespace().collect::<Vec<_>>();
if parts.len() == 3 && parts[1] == "LUA_VERSION_NUM" {
version_found = parts[2].to_string();
}
}
let link_lib = match env::var("LUA_LINK") {
Ok(s) if s == "static" => "static=",
_ => "",
};
if cfg!(any(not(feature = "module"), target_os = "windows")) {
println!(
"cargo:rustc-link-search=native={}",
lib_dir.as_ref().display()
);
println!(
"cargo:rustc-link-lib={}{}",
link_lib,
lua_lib.as_ref().display()
);
}
Ok(version_found)
}
+109 -2
View File
@@ -1,7 +1,8 @@
#![allow(unreachable_code)]
use std::env;
use std::io::{Error, ErrorKind, Result};
use std::fs::File;
use std::io::{Error, ErrorKind, Result, Write};
use std::path::{Path, PathBuf};
use std::process::Command;
@@ -91,6 +92,105 @@ fn build_glue<P: AsRef<Path> + std::fmt::Debug>(include_path: &P) {
.unwrap();
}
// When cross-compiling, we cannot use `build_glue` as we cannot run the generated
// executable. Instead, let's take a stab at synthesizing the likely values.
// If you're cross-compiling and using a non-vendored library then there is a chance
// that the values selected here may be incorrect, but we have no way to determine
// that here.
fn generate_glue() -> Result<()> {
let build_dir = PathBuf::from(env::var_os("OUT_DIR").unwrap());
let mut glue = File::create(build_dir.join("glue.rs"))?;
writeln!(
glue,
"/* This file was generated by build/main.rs; do not modify by hand */"
)?;
writeln!(glue, "use std::os::raw::*;")?;
writeln!(glue, "/* luaconf.h */")?;
let pointer_bit_width: usize = env::var("CARGO_CFG_TARGET_POINTER_WIDTH")
.unwrap()
.parse()
.unwrap();
writeln!(
glue,
"pub const LUA_EXTRASPACE: c_int = {} / 8;",
pointer_bit_width
)?;
// This is generally hardcoded to this size
writeln!(glue, "pub const LUA_IDSIZE: c_int = 60;")?;
// Unless the target is restricted, the defaults are 64 bit
writeln!(glue, "pub type LUA_NUMBER = c_double;")?;
writeln!(glue, "pub type LUA_INTEGER = i64;")?;
writeln!(glue, "pub type LUA_UNSIGNED = u64;")?;
writeln!(glue, "/* lua.h */")?;
let version = if cfg!(any(feature = "luajit", feature = "lua51")) {
(5, 1, 0)
} else if cfg!(feature = "lua52") {
(5, 2, 0)
} else if cfg!(feature = "lua53") {
(5, 3, 0)
} else if cfg!(feature = "lua54") {
(5, 4, 0)
} else {
unreachable!();
};
writeln!(
glue,
"pub const LUA_VERSION_NUM: c_int = {};",
(version.0 * 100) + version.1
)?;
let max_stack = if pointer_bit_width >= 32 {
1_000_000
} else {
15_000
};
writeln!(
glue,
"pub const LUA_REGISTRYINDEX: c_int = -{} - 1000;",
max_stack
)?;
// These two are only defined in lua 5.1
writeln!(glue, "pub const LUA_ENVIRONINDEX: c_int = -10001;")?;
writeln!(glue, "pub const LUA_GLOBALSINDEX: c_int = -10002;")?;
writeln!(glue, "/* lauxlib.h */")?;
// This is only defined in lua 5.3 and up, but we can always generate its value here,
// even if we don't use it.
// This matches the default definition in lauxlib.h
writeln!(glue, "pub const LUAL_NUMSIZES: c_int = std::mem::size_of::<LUA_INTEGER>() as c_int * 16 + std::mem::size_of::<LUA_NUMBER>() as c_int;")?;
writeln!(glue, "/* lualib.h */")?;
write!(
glue,
r#"
#[cfg(feature = "luajit")]
pub const LUA_BITLIBNAME: &str = "bit";
#[cfg(not(feature = "luajit"))]
pub const LUA_BITLIBNAME: &str = "bit32";
pub const LUA_COLIBNAME: &str = "coroutine";
pub const LUA_DBLIBNAME: &str = "debug";
pub const LUA_IOLIBNAME: &str = "io";
pub const LUA_LOADLIBNAME: &str = "package";
pub const LUA_MATHLIBNAME: &str = "math";
pub const LUA_OSLIBNAME: &str = "os";
pub const LUA_STRLIBNAME: &str = "string";
pub const LUA_TABLIBNAME: &str = "table";
pub const LUA_UTF8LIBNAME: &str = "utf8";
pub const LUA_JITLIBNAME: &str = "jit";
pub const LUA_FFILIBNAME: &str = "ffi";
"#
)?;
Ok(())
}
fn main() {
#[cfg(not(any(
feature = "lua54",
@@ -132,5 +232,12 @@ fn main() {
);
let include_dir = find::probe_lua();
build_glue(&include_dir);
if env::var("TARGET").unwrap() != env::var("HOST").unwrap() {
generate_glue().unwrap();
} else {
build_glue(&include_dir);
println!("cargo:rerun-if-changed=src/ffi/glue/glue.c");
}
println!("cargo:rerun-if-changed=build");
}
+6 -5
View File
@@ -2,8 +2,9 @@ use std::collections::HashMap;
use std::sync::Arc;
use bstr::BString;
use hyper::{body::Body as HyperBody, Client as HyperClient};
use tokio::{stream::StreamExt, sync::Mutex};
use hyper::body::{Body as HyperBody, HttpBody as _};
use hyper::Client as HyperClient;
use tokio::sync::Mutex;
use mlua::{Error, Lua, Result, UserData, UserDataMethods};
@@ -20,8 +21,8 @@ impl UserData for BodyReader {
fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) {
methods.add_async_method("read", |_, reader, ()| async move {
let mut reader = reader.0.lock().await;
let bytes = reader.try_next().await.map_err(Error::external)?;
if let Some(bytes) = bytes {
if let Some(bytes) = reader.data().await {
let bytes = bytes.map_err(Error::external)?;
return Ok(Some(BString::from(bytes.as_ref())));
}
Ok(None)
@@ -61,7 +62,7 @@ async fn main() -> Result<()> {
let f = lua
.load(
r#"
local res = fetch_url(...);
local res = fetch_url(...)
print(res.status)
for key, vals in pairs(res.headers) do
for _, val in ipairs(vals) do
+41
View File
@@ -0,0 +1,41 @@
use mlua::{Error, Lua, LuaSerdeExt, Result};
#[tokio::main]
async fn main() -> Result<()> {
let lua = Lua::new();
let globals = lua.globals();
globals.set("null", lua.null()?)?;
let fetch_json = lua.create_async_function(|lua, uri: String| async move {
let resp = reqwest::get(&uri)
.await
.and_then(|resp| resp.error_for_status())
.map_err(Error::external)?;
let json = resp
.json::<serde_json::Value>()
.await
.map_err(Error::external)?;
lua.to_value(&json)
})?;
globals.set("fetch_json", fetch_json)?;
let f = lua
.load(
r#"
function print_r(t, indent)
local indent = indent or ''
for k, v in pairs(t) do
io.write(indent, tostring(k))
if type(v) == "table" then io.write(':\n') print_r(v, indent..' ')
else io.write(': ', v == null and "null" or tostring(v), '\n') end
end
end
local res = fetch_json(...)
print_r(res)
"#,
)
.into_function()?;
f.call_async("https://httpbin.org/anything?arg0=val0").await
}
+3 -4
View File
@@ -1,9 +1,8 @@
use std::net::Shutdown;
use std::sync::Arc;
use bstr::BString;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::net::{TcpListener, TcpStream};
use tokio::prelude::*;
use tokio::sync::Mutex;
use tokio::task;
@@ -55,7 +54,7 @@ impl UserData for LuaTcpStream {
});
methods.add_async_method("close", |_, stream, ()| async move {
stream.0.lock().await.shutdown(Shutdown::Both)?;
stream.0.lock().await.shutdown().await?;
Ok(())
});
}
@@ -63,7 +62,7 @@ impl UserData for LuaTcpStream {
async fn run_server(lua: &'static Lua) -> Result<()> {
let spawn = lua.create_function(move |_, func: Function| {
task::spawn_local(async move { func.call_async::<_, ()>(()).await.unwrap() });
task::spawn_local(async move { func.call_async::<_, ()>(()).await });
Ok(())
})?;
+27
View File
@@ -164,6 +164,33 @@ fn main() -> Result<()> {
< f32::EPSILON
);
// Normally, Rust types passed to `Lua` must be `'static`, because there is no way to be
// sure of their lifetime inside the Lua state. There is, however, a limited way to lift this
// requirement. You can call `Lua::scope` to create userdata and callbacks types that only live
// for as long as the call to scope, but do not have to be `'static` (and `Send`).
{
let mut rust_val = 0;
lua.scope(|scope| {
// We create a 'sketchy' Lua callback that holds a mutable reference to the variable
// `rust_val`. Outside of a `Lua::scope` call, this would not be allowed
// because it could be unsafe.
lua.globals().set(
"sketchy",
scope.create_function_mut(|_, ()| {
rust_val = 42;
Ok(())
})?,
)?;
lua.load("sketchy()").exec()
})?;
assert_eq!(rust_val, 42);
}
// We were able to run our 'sketchy' function inside the scope just fine. However, if we
// try to run our 'sketchy' function outside of the scope, the function we created will have
// been invalidated and we will generate an error. If our function wasn't invalidated, we
-1
View File
@@ -17,4 +17,3 @@ vendored = ["mlua/vendored"]
[dependencies]
mlua = { path = "../..", features = ["module"] }
mlua_derive = { path = "../../mlua_derive" }
+1 -1
View File
@@ -8,7 +8,7 @@ fn used_memory(lua: &Lua, _: ()) -> LuaResult<usize> {
Ok(lua.used_memory())
}
#[mlua_derive::lua_module]
#[mlua::lua_module]
fn rust_module(lua: &Lua) -> LuaResult<LuaTable> {
let exports = lua.create_table()?;
exports.set("sum", lua.create_function(sum)?)?;
+66
View File
@@ -0,0 +1,66 @@
use mlua::{Error, Lua, LuaSerdeExt, Result, UserData, Value};
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize)]
enum Transmission {
Manual,
Automatic,
}
#[derive(Serialize, Deserialize)]
struct Engine {
v: u32,
kw: u32,
}
#[derive(Serialize, Deserialize)]
struct Car {
active: bool,
model: String,
transmission: Transmission,
engine: Engine,
}
impl UserData for Car {}
fn main() -> Result<()> {
let lua = Lua::new();
let globals = lua.globals();
// Create Car struct from a Lua table
let car: Car = lua.from_value(lua.load(r#"
{active = true, model = "Volkswagen Golf", transmission = "Automatic", engine = {v = 1499, kw = 90}}
"#).eval()?)?;
// Set it as (serializable) userdata
globals.set("null", lua.null()?)?;
globals.set("array_mt", lua.array_metatable()?)?;
globals.set("car", lua.create_ser_userdata(car)?)?;
// Create a Lua table with multiple data types
let val: Value = lua
.load(r#"{driver = "Boris", car = car, price = null, points = setmetatable({}, array_mt)}"#)
.eval()?;
// Serialize the table above to JSON
let json_str = serde_json::to_string(&val).map_err(Error::external)?;
println!("{}", json_str);
// Create Lua Value from JSON (or any serializable type)
let json = serde_json::json!({
"key": "value",
"null": null,
"array": [],
});
globals.set("json_value", lua.to_value(&json)?)?;
lua.load(
r#"
assert(json_value["key"] == "value")
assert(json_value["null"] == null)
assert(#(json_value["array"]) == 0)
"#,
)
.exec()?;
Ok(())
}
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "mlua_derive"
version = "0.4.0"
version = "0.5.0"
authors = ["Aleksandr Orlenko <zxteam@pm.me>"]
edition = "2018"
description = "Procedural macros for the mlua crate."
+72 -3
View File
@@ -1,4 +1,5 @@
use std::collections::{BTreeMap, HashMap};
use std::borrow::Cow;
use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
use std::ffi::{CStr, CString};
use std::hash::{BuildHasher, Hash};
use std::string::String as StdString;
@@ -12,7 +13,7 @@ use crate::lua::Lua;
use crate::string::String;
use crate::table::Table;
use crate::thread::Thread;
use crate::types::{LightUserData, MaybeSend, Number};
use crate::types::{LightUserData, MaybeSend};
use crate::userdata::{AnyUserData, UserData};
use crate::value::{FromLua, Nil, ToLua, Value};
@@ -222,6 +223,12 @@ impl<'lua> ToLua<'lua> for &str {
}
}
impl<'lua> ToLua<'lua> for Cow<'_, str> {
fn to_lua(self, lua: &'lua Lua) -> Result<Value<'lua>> {
Ok(Value::String(lua.create_string(self.as_bytes())?))
}
}
impl<'lua> ToLua<'lua> for CString {
fn to_lua(self, lua: &'lua Lua) -> Result<Value<'lua>> {
Ok(Value::String(lua.create_string(self.as_bytes())?))
@@ -256,6 +263,12 @@ impl<'lua> ToLua<'lua> for &CStr {
}
}
impl<'lua> ToLua<'lua> for Cow<'_, CStr> {
fn to_lua(self, lua: &'lua Lua) -> Result<Value<'lua>> {
Ok(Value::String(lua.create_string(self.to_bytes())?))
}
}
impl<'lua> ToLua<'lua> for BString {
fn to_lua(self, lua: &'lua Lua) -> Result<Value<'lua>> {
Ok(Value::String(lua.create_string(&self)?))
@@ -345,7 +358,13 @@ macro_rules! lua_convert_float {
($x:ty) => {
impl<'lua> ToLua<'lua> for $x {
fn to_lua(self, _: &'lua Lua) -> Result<Value<'lua>> {
Ok(Value::Number(self as Number))
cast(self)
.ok_or_else(|| Error::ToLuaConversionError {
from: stringify!($x),
to: "number",
message: Some("out of range".to_string()),
})
.map(Value::Number)
}
}
@@ -481,6 +500,56 @@ impl<'lua, K: Ord + FromLua<'lua>, V: FromLua<'lua>> FromLua<'lua> for BTreeMap<
}
}
impl<'lua, T: Eq + Hash + ToLua<'lua>, S: BuildHasher> ToLua<'lua> for HashSet<T, S> {
fn to_lua(self, lua: &'lua Lua) -> Result<Value<'lua>> {
Ok(Value::Table(lua.create_table_from(
self.into_iter().map(|val| (val, true)),
)?))
}
}
impl<'lua, T: Eq + Hash + FromLua<'lua>, S: BuildHasher + Default> FromLua<'lua> for HashSet<T, S> {
fn from_lua(value: Value<'lua>, _: &'lua Lua) -> Result<Self> {
if let Value::Table(table) = value {
table
.pairs::<T, Value<'lua>>()
.map(|res| res.map(|(k, _)| k))
.collect()
} else {
Err(Error::FromLuaConversionError {
from: value.type_name(),
to: "HashSet",
message: Some("expected table".to_string()),
})
}
}
}
impl<'lua, T: Ord + ToLua<'lua>> ToLua<'lua> for BTreeSet<T> {
fn to_lua(self, lua: &'lua Lua) -> Result<Value<'lua>> {
Ok(Value::Table(lua.create_table_from(
self.into_iter().map(|val| (val, true)),
)?))
}
}
impl<'lua, T: Ord + FromLua<'lua>> FromLua<'lua> for BTreeSet<T> {
fn from_lua(value: Value<'lua>, _: &'lua Lua) -> Result<Self> {
if let Value::Table(table) = value {
table
.pairs::<T, Value<'lua>>()
.map(|res| res.map(|(k, _)| k))
.collect()
} else {
Err(Error::FromLuaConversionError {
from: value.type_name(),
to: "BTreeSet",
message: Some("expected table".to_string()),
})
}
}
}
impl<'lua, T: ToLua<'lua>> ToLua<'lua> for Option<T> {
fn to_lua(self, lua: &'lua Lua) -> Result<Value<'lua>> {
match self {
+47
View File
@@ -9,6 +9,7 @@ use std::sync::Arc;
/// Error type returned by `mlua` methods.
#[derive(Debug, Clone)]
#[non_exhaustive]
pub enum Error {
/// Syntax error while parsing Lua source code.
SyntaxError {
@@ -105,6 +106,13 @@ pub enum Error {
/// [`AnyUserData`]: struct.AnyUserData.html
/// [`UserDataMethods`]: trait.UserDataMethods.html
UserDataTypeMismatch,
/// An [`AnyUserData`] borrow failed because it has been destructed.
///
/// This error can happen either due to to being destructed in a previous __gc, or due to being
/// destructed from exiting a `Lua::scope` call.
///
/// [`AnyUserData`]: struct.AnyUserData.html
UserDataDestructed,
/// An [`AnyUserData`] immutable borrow failed because it is already borrowed mutably.
///
/// This error can occur when a method on a [`UserData`] type calls back into Lua, which then
@@ -132,6 +140,19 @@ pub enum Error {
/// Original error returned by the Rust code.
cause: Arc<Error>,
},
/// A Rust panic that was previosly resumed, returned again.
///
/// This error can occur only when a Rust panic resumed previously was recovered
/// and returned again.
PreviouslyResumedPanic,
/// Serialization error.
#[cfg(feature = "serialize")]
#[cfg_attr(docsrs, doc(cfg(feature = "serialize")))]
SerializeError(StdString),
/// Deserialization error.
#[cfg(feature = "serialize")]
#[cfg_attr(docsrs, doc(cfg(feature = "serialize")))]
DeserializeError(StdString),
/// A custom error.
///
/// This can be used for returning user-defined errors from callbacks.
@@ -206,6 +227,7 @@ impl fmt::Display for Error {
}
Error::CoroutineInactive => write!(fmt, "cannot resume inactive coroutine"),
Error::UserDataTypeMismatch => write!(fmt, "userdata is not expected type"),
Error::UserDataDestructed => write!(fmt, "userdata has been destructed"),
Error::UserDataBorrowError => write!(fmt, "userdata already mutably borrowed"),
Error::UserDataBorrowMutError => write!(fmt, "userdata already borrowed"),
Error::MismatchedRegistryKey => {
@@ -214,6 +236,17 @@ impl fmt::Display for Error {
Error::CallbackError { ref traceback, .. } => {
write!(fmt, "callback error: {}", traceback)
}
Error::PreviouslyResumedPanic => {
write!(fmt, "previously resumed panic returned again")
}
#[cfg(feature = "serialize")]
Error::SerializeError(ref err) => {
write!(fmt, "serialize error: {}", err)
},
#[cfg(feature = "serialize")]
Error::DeserializeError(ref err) => {
write!(fmt, "deserialize error: {}", err)
},
Error::ExternalError(ref err) => write!(fmt, "{}", err),
}
}
@@ -289,3 +322,17 @@ impl std::convert::From<Utf8Error> for Error {
Error::external(err)
}
}
#[cfg(feature = "serialize")]
impl serde::ser::Error for Error {
fn custom<T: fmt::Display>(msg: T) -> Self {
Self::SerializeError(msg.to_string())
}
}
#[cfg(feature = "serialize")]
impl serde::de::Error for Error {
fn custom<T: fmt::Display>(msg: T) -> Self {
Self::DeserializeError(msg.to_string())
}
}
+1 -1
View File
@@ -1,6 +1,6 @@
// The MIT License (MIT)
//
// Copyright (c) 2019 A. Orlenko
// Copyright (c) 2019-2021 A. Orlenko
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
+1 -7
View File
@@ -1,6 +1,6 @@
// The MIT License (MIT)
//
// Copyright (c) 2019-2020 A. Orlenko
// Copyright (c) 2019-2021 A. Orlenko
// Copyright (c) 2014 J.C. Moyer
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
@@ -225,12 +225,8 @@ int main(int argc, const char **argv) {
// == luaconf.h ==========================================================
RS_COMMENT("luaconf.h"),
RS_STR("LUA_PATH_DEFAULT", LUA_PATH_DEFAULT),
RS_STR("LUA_CPATH_DEFAULT", LUA_CPATH_DEFAULT),
RS_STR("LUA_DIRSEP", LUA_DIRSEP),
RS_INT("LUA_EXTRASPACE", LUA_EXTRASPACE),
RS_INT("LUA_IDSIZE", LUA_IDSIZE),
RS_INT("LUAL_BUFFERSIZE", LUAL_BUFFERSIZE),
RS_TYPE("LUA_NUMBER",
sizeof(LUA_NUMBER) > sizeof(float) ? "c_double" : "c_float"),
RS_TYPE("LUA_INTEGER", rs_int_type(sizeof(LUA_INTEGER))),
@@ -244,8 +240,6 @@ int main(int argc, const char **argv) {
RS_COMMENT("lua.h"),
RS_INT("LUA_VERSION_NUM", LUA_VERSION_NUM),
RS_STR("LUA_VERSION", LUA_VERSION),
RS_STR("LUA_RELEASE", LUA_RELEASE),
RS_INT("LUA_REGISTRYINDEX", LUA_REGISTRYINDEX),
#if LUA_VERSION_NUM == 501
RS_INT("LUA_ENVIRONINDEX", LUA_ENVIRONINDEX),
+1 -1
View File
@@ -1,6 +1,6 @@
// The MIT License (MIT)
//
// Copyright (c) 2019-2020 A. Orlenko
// Copyright (c) 2019-2021 A. Orlenko
// Copyright (c) 2014 J.C. Moyer
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
+5 -4
View File
@@ -1,6 +1,6 @@
// The MIT License (MIT)
//
// Copyright (c) 2019-2020 A. Orlenko
// Copyright (c) 2019-2021 A. Orlenko
// Copyright (c) 2014 J.C. Moyer
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
@@ -32,13 +32,14 @@ use std::ptr;
use super::luaconf;
pub use super::glue::{LUA_RELEASE, LUA_VERSION, LUA_VERSION_NUM};
pub use super::glue::LUA_REGISTRYINDEX;
#[cfg(any(feature = "lua51", feature = "luajit"))]
pub use super::glue::{LUA_ENVIRONINDEX, LUA_GLOBALSINDEX};
pub use super::glue::{LUA_REGISTRYINDEX, LUA_VERSION_NUM};
#[cfg(not(feature = "luajit"))]
pub const LUA_SIGNATURE: &[u8] = b"\x1bLua";
#[cfg(feature = "luajit")]
pub const LUA_SIGNATURE: &[u8] = b"\x1bLJ";
// option for multiple returns in 'lua_pcall' and 'lua_call'
pub const LUA_MULTRET: c_int = -1;
+1 -2
View File
@@ -1,6 +1,6 @@
// The MIT License (MIT)
//
// Copyright (c) 2019-2020 A. Orlenko
// Copyright (c) 2019-2021 A. Orlenko
// Copyright (c) 2014 J.C. Moyer
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
@@ -23,7 +23,6 @@
//! Contains definitions from `luaconf.h`.
pub use super::glue::LUAL_BUFFERSIZE;
pub use super::glue::LUA_INTEGER;
pub use super::glue::LUA_NUMBER;
pub use super::glue::LUA_UNSIGNED;
+1 -1
View File
@@ -1,6 +1,6 @@
// The MIT License (MIT)
//
// Copyright (c) 2019-2020 A. Orlenko
// Copyright (c) 2019-2021 A. Orlenko
// Copyright (c) 2014 J.C. Moyer
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
+3 -3
View File
@@ -1,6 +1,6 @@
// The MIT License (MIT)
//
// Copyright (c) 2019-2020 A. Orlenko
// Copyright (c) 2019-2021 A. Orlenko
// Copyright (c) 2014 J.C. Moyer
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
@@ -211,8 +211,8 @@ pub use self::lua::{
LUA_HOOKCOUNT, LUA_HOOKLINE, LUA_HOOKRET, LUA_HOOKTAILCALL, LUA_MASKCALL, LUA_MASKCOUNT,
LUA_MASKLINE, LUA_MASKRET, LUA_MINSTACK, LUA_MULTRET, LUA_OK, LUA_OPADD, LUA_OPDIV, LUA_OPEQ,
LUA_OPLE, LUA_OPLT, LUA_OPMOD, LUA_OPMUL, LUA_OPPOW, LUA_OPSUB, LUA_OPUNM, LUA_REGISTRYINDEX,
LUA_TBOOLEAN, LUA_TFUNCTION, LUA_TLIGHTUSERDATA, LUA_TNIL, LUA_TNONE, LUA_TNUMBER, LUA_TSTRING,
LUA_TTABLE, LUA_TTHREAD, LUA_TUSERDATA, LUA_YIELD,
LUA_SIGNATURE, LUA_TBOOLEAN, LUA_TFUNCTION, LUA_TLIGHTUSERDATA, LUA_TNIL, LUA_TNONE,
LUA_TNUMBER, LUA_TSTRING, LUA_TTABLE, LUA_TTHREAD, LUA_TUSERDATA, LUA_YIELD,
};
#[cfg(any(feature = "lua54", feature = "lua53"))]
+39 -2
View File
@@ -1,5 +1,5 @@
use std::os::raw::c_int;
use std::ptr;
use std::os::raw::{c_int, c_void};
use std::{ptr, slice};
use crate::error::{Error, Result};
use crate::ffi;
@@ -119,6 +119,7 @@ impl<'lua> Function<'lua> {
///
/// [`AsyncThread`]: struct.AsyncThread.html
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
pub fn call_async<'fut, A, R>(&self, args: A) -> LocalBoxFuture<'fut, Result<R>>
where
'lua: 'fut,
@@ -205,6 +206,42 @@ impl<'lua> Function<'lua> {
Ok(Function(lua.pop_ref()))
}
}
/// Dumps the function as a binary chunk.
///
/// If `strip` is true, the binary representation may not include all debug information
/// about the function, to save space.
pub fn dump(&self, strip: bool) -> Result<Vec<u8>> {
unsafe extern "C" fn writer(
_state: *mut ffi::lua_State,
buf: *const c_void,
buf_len: usize,
data: *mut c_void,
) -> c_int {
let data = &mut *(data as *mut Vec<u8>);
let buf = slice::from_raw_parts(buf as *const u8, buf_len);
data.extend_from_slice(buf);
0
}
let lua = self.0.lua;
let mut data: Vec<u8> = Vec::new();
unsafe {
let _sg = StackGuard::new(lua.state);
assert_stack(lua.state, 1);
lua.push_ref(&self.0);
let strip = if strip { 1 } else { 0 };
ffi::lua_dump(
lua.state,
writer,
&mut data as *mut Vec<u8> as *mut c_void,
strip,
);
ffi::lua_pop(lua.state, 1);
}
Ok(data)
}
}
impl<'lua> PartialEq for Function<'lua> {
+1 -1
View File
@@ -37,7 +37,7 @@ impl<'a> Debug<'a> {
}
}
/// Corresponds to the `n` what mask.
/// Corresponds to the `S` what mask.
pub fn source(&self) -> DebugSource<'a> {
unsafe {
mlua_assert!(
+37 -2
View File
@@ -24,6 +24,18 @@
//! The [`UserData`] trait can be implemented by user-defined types to make them available to Lua.
//! Methods and operators to be used from Lua can be added using the [`UserDataMethods`] API.
//!
//! # Serde support
//!
//! The [`LuaSerdeExt`] trait implemented for [`Lua`] allows conversion from Rust types to Lua values
//! and vice versa using serde. Any user defined data type that implements [`serde::Serialize`] or
//! [`serde::Deserialize`] can be converted.
//! For convenience, additional functionality to handle `NULL` values and arrays is provided.
//!
//! The [`Value`] enum implements [`serde::Serialize`] trait to support serializing Lua values
//! (including [`UserData`]) into Rust values.
//!
//! Requires `feature = "serialize"`.
//!
//! # Async/await support
//!
//! The [`create_async_function`] allows creating non-blocking functions that returns [`Future`].
@@ -34,7 +46,7 @@
//!
//! # `Send` requirement
//! By default `mlua` is `!Send`. This can be changed by enabling `feature = "send"` that adds `Send` requirement
//! to `Function`s and [`UserData`].
//! to [`Function`]s and [`UserData`].
//!
//! [Lua programming language]: https://www.lua.org/
//! [`Lua`]: struct.Lua.html
@@ -45,16 +57,24 @@
//! [`FromLua`]: trait.FromLua.html
//! [`ToLuaMulti`]: trait.ToLuaMulti.html
//! [`FromLuaMulti`]: trait.FromLuaMulti.html
//! [`Function`]: struct.Function.html
//! [`UserData`]: trait.UserData.html
//! [`UserDataMethods`]: trait.UserDataMethods.html
//! [`LuaSerdeExt`]: serde/trait.LuaSerdeExt.html
//! [`Value`]: enum.Value.html
//! [`create_async_function`]: struct.Lua.html#method.create_async_function
//! [`call_async`]: struct.Function.html#method.call_async
//! [`AsyncThread`]: struct.AsyncThread.html
//! [`Future`]: ../futures_core/future/trait.Future.html
//! [`serde::Serialize`]: https://docs.serde.rs/serde/ser/trait.Serialize.html
//! [`serde::Deserialize`]: https://docs.serde.rs/serde/de/trait.Deserialize.html
// mlua types in rustdoc of other crates get linked to here.
#![doc(html_root_url = "https://docs.rs/mlua/0.5.4")]
// Deny warnings inside doc tests / examples. When this isn't present, rustdoc doesn't show *any*
// warnings at all.
#![doc(test(attr(deny(warnings))))]
#![cfg_attr(docsrs, feature(doc_cfg))]
#[macro_use]
mod macros;
@@ -81,7 +101,7 @@ pub use crate::ffi::lua_State;
pub use crate::error::{Error, ExternalError, ExternalResult, Result};
pub use crate::function::Function;
pub use crate::hook::{Debug, DebugNames, DebugSource, DebugStack, HookTriggers};
pub use crate::lua::{Chunk, GCMode, Lua};
pub use crate::lua::{Chunk, ChunkMode, GCMode, Lua};
pub use crate::multi::Variadic;
pub use crate::scope::Scope;
pub use crate::stdlib::StdLib;
@@ -95,4 +115,19 @@ pub use crate::value::{FromLua, FromLuaMulti, MultiValue, Nil, ToLua, ToLuaMulti
#[cfg(feature = "async")]
pub use crate::thread::AsyncThread;
#[cfg(feature = "serialize")]
pub use crate::serde::LuaSerdeExt;
pub mod prelude;
#[cfg(feature = "serialize")]
#[cfg_attr(docsrs, doc(cfg(feature = "serialize")))]
pub mod serde;
// Re-export #[mlua_derive::lua_module].
#[cfg(feature = "mlua_derive")]
#[allow(unused_imports)]
#[macro_use]
extern crate mlua_derive;
#[cfg(feature = "mlua_derive")]
#[doc(hidden)]
pub use mlua_derive::*;
+253 -92
View File
@@ -1,9 +1,10 @@
use std::any::TypeId;
use std::cell::{RefCell, UnsafeCell};
use std::collections::HashMap;
use std::collections::{HashMap, HashSet};
use std::ffi::CString;
use std::marker::PhantomData;
use std::os::raw::{c_char, c_int, c_void};
use std::panic::resume_unwind;
use std::sync::{Arc, Mutex, Weak};
use std::{mem, ptr, str};
@@ -18,13 +19,14 @@ use crate::table::Table;
use crate::thread::Thread;
use crate::types::{
Callback, HookCallback, Integer, LightUserData, LuaRef, MaybeSend, Number, RegistryKey,
UserDataCell,
};
use crate::userdata::{AnyUserData, MetaMethod, UserData, UserDataMethods};
use crate::userdata::{AnyUserData, MetaMethod, UserData, UserDataMethods, UserDataWrapped};
use crate::util::{
assert_stack, callback_error, check_stack, get_gc_userdata, get_main_state,
get_meta_gc_userdata, get_wrapped_error, init_error_registry, init_gc_metatable_for,
init_userdata_metatable, pop_error, protect_lua, protect_lua_closure, push_gc_userdata,
push_meta_gc_userdata, push_string, push_userdata, push_wrapped_error, StackGuard,
assert_stack, callback_error, check_stack, get_gc_userdata, get_main_state, get_userdata,
get_wrapped_error, init_error_registry, init_gc_metatable_for, init_userdata_metatable,
pop_error, protect_lua, protect_lua_closure, push_gc_userdata, push_meta_gc_userdata,
push_string, push_userdata, push_wrapped_error, StackGuard, WrappedPanic,
};
use crate::value::{FromLua, FromLuaMulti, MultiValue, Nil, ToLua, ToLuaMulti, Value};
@@ -39,6 +41,9 @@ use {
futures_util::future::{self, TryFutureExt},
};
#[cfg(feature = "serialize")]
use {crate::util::get_destructed_userdata_metatable, serde::Serialize};
/// Top level Lua struct which holds the Lua state itself.
pub struct Lua {
pub(crate) state: *mut ffi::lua_State,
@@ -53,8 +58,10 @@ pub struct Lua {
// Data associated with the lua_State.
struct ExtraData {
registered_userdata: HashMap<TypeId, c_int>,
registered_userdata_mt: HashSet<isize>,
registry_unref_list: Arc<Mutex<Option<Vec<c_int>>>>,
libs: StdLib,
mem_info: *mut MemoryInfo,
ref_thread: *mut ffi::lua_State,
@@ -94,19 +101,22 @@ pub(crate) static EXTRA_REGISTRY_KEY: u8 = 0;
/// Requires `feature = "send"`
#[cfg(feature = "send")]
#[cfg_attr(docsrs, doc(cfg(feature = "send")))]
unsafe impl Send for Lua {}
impl Drop for Lua {
fn drop(&mut self) {
unsafe {
if !self.ephemeral {
let mut extra = mlua_expect!(self.extra.lock(), "extra is poisoned");
let extra = mlua_expect!(self.extra.lock(), "extra is poisoned");
mlua_debug_assert!(
ffi::lua_gettop(extra.ref_thread) == extra.ref_stack_max
&& extra.ref_stack_max as usize == extra.ref_free.len(),
"reference leak detected"
);
*mlua_expect!(extra.registry_unref_list.lock(), "unref list poisoned") = None;
let mut unref_list =
mlua_expect!(extra.registry_unref_list.lock(), "unref list poisoned");
*unref_list = None;
ffi::lua_close(self.main_state.expect("main_state is null"));
if !extra.mem_info.is_null() {
Box::from_raw(extra.mem_info);
@@ -164,7 +174,9 @@ impl Lua {
let mut lua = unsafe { Self::unsafe_new_with(libs) };
mlua_expect!(lua.disable_c_modules(), "Error during disabling C modules");
if libs.contains(StdLib::PACKAGE) {
mlua_expect!(lua.disable_c_modules(), "Error during disabling C modules");
}
lua.safe = true;
Ok(lua)
@@ -254,7 +266,7 @@ impl Lua {
lua.ephemeral = false;
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))]
{
lua.extra.lock().unwrap().mem_info = mem_info;
mlua_expect!(lua.extra.lock(), "extra is poisoned").mem_info = mem_info;
}
mlua_expect!(
@@ -263,6 +275,7 @@ impl Lua {
}),
"Error during loading standard libraries"
);
mlua_expect!(lua.extra.lock(), "extra is poisoned").libs |= libs;
lua
}
@@ -292,6 +305,10 @@ impl Lua {
init_gc_metatable_for::<Waker>(state, None);
}
// Init serde metatables
#[cfg(feature = "serialize")]
crate::serde::init_metatables(state);
// Create ref stack thread and place it in the registry to prevent it from being garbage
// collected.
@@ -306,8 +323,10 @@ impl Lua {
let extra = Arc::new(Mutex::new(ExtraData {
registered_userdata: HashMap::new(),
registered_userdata_mt: HashSet::new(),
registry_unref_list: Arc::new(Mutex::new(Some(Vec::new()))),
ref_thread,
libs: StdLib::NONE,
mem_info: ptr::null_mut(),
// We need 1 extra stack space to move values in and out of the ref stack.
ref_stack_size: ffi::LUA_MINSTACK - 1,
@@ -368,11 +387,20 @@ impl Lua {
}
let state = self.main_state.unwrap_or(self.state);
unsafe {
let res = unsafe {
protect_lua_closure(state, 0, 0, |state| {
load_from_std_lib(state, libs);
})
};
// If `package` library loaded into a safe lua state then disable C modules
let curr_libs = mlua_expect!(self.extra.lock(), "extra is poisoned").libs;
if self.safe && (curr_libs ^ (curr_libs | libs)).contains(StdLib::PACKAGE) {
mlua_expect!(self.disable_c_modules(), "Error during disabling C modules");
}
mlua_expect!(self.extra.lock(), "extra is poisoned").libs |= libs;
res
}
/// Consumes and leaks `Lua` object, returning a static reference `&'static Lua`.
@@ -490,7 +518,7 @@ impl Lua {
unsafe { (*extra.mem_info).used_memory as usize }
}
/// Sets a memory limit on this Lua state.
/// Sets a memory limit (in bytes) on this Lua state.
///
/// Once an allocation occurs that would pass this memory limit,
/// a `Error::MemoryError` is generated instead.
@@ -658,6 +686,9 @@ impl Lua {
/// similar on the returned builder. Code is not even parsed until one of these methods is
/// called.
///
/// If this `Lua` was created with `unsafe_new`, `load` will automatically detect and load
/// chunks of either text or binary type, as if passing `bt` mode to `luaL_loadbufferx`.
///
/// [`Chunk::exec`]: struct.Chunk.html#method.exec
pub fn load<'lua, 'a, S>(&'lua self, source: &'a S) -> Chunk<'lua, 'a>
where
@@ -668,6 +699,7 @@ impl Lua {
source: source.as_ref(),
name: None,
env: None,
mode: None,
}
}
@@ -676,28 +708,35 @@ impl Lua {
source: &[u8],
name: Option<&CString>,
env: Option<Value<'lua>>,
mode: Option<ChunkMode>,
) -> Result<Function<'lua>> {
unsafe {
let _sg = StackGuard::new(self.state);
assert_stack(self.state, 1);
match if let Some(name) = name {
ffi::luaL_loadbufferx(
self.state,
source.as_ptr() as *const c_char,
source.len(),
name.as_ptr() as *const c_char,
cstr!("t"),
)
} else {
ffi::luaL_loadbufferx(
self.state,
source.as_ptr() as *const c_char,
source.len(),
ptr::null(),
cstr!("t"),
)
} {
let mode_str = match mode {
Some(ChunkMode::Binary) if self.safe => {
return Err(Error::SafetyError(
"binary chunks are disabled in safe mode".to_string(),
))
}
Some(ChunkMode::Binary) => cstr!("b"),
Some(ChunkMode::Text) => cstr!("t"),
None if source.starts_with(ffi::LUA_SIGNATURE) && self.safe => {
return Err(Error::SafetyError(
"binary chunks are disabled in safe mode".to_string(),
))
}
None => cstr!("bt"),
};
match ffi::luaL_loadbufferx(
self.state,
source.as_ptr() as *const c_char,
source.len(),
name.map(|n| n.as_ptr()).unwrap_or_else(ptr::null),
mode_str,
) {
ffi::LUA_OK => {
if let Some(env) = env {
self.push_value(env)?;
@@ -728,7 +767,7 @@ impl Lua {
}
}
/// Creates and returns a new table.
/// Creates and returns a new empty table.
pub fn create_table(&self) -> Result<Table> {
unsafe {
let _sg = StackGuard::new(self.state);
@@ -742,6 +781,21 @@ impl Lua {
}
}
/// Creates and returns a new empty table, with the specified capacity.
/// `narr` is a hint for how many elements the table will have as a sequence;
/// `nrec` is a hint for how many other elements the table will have.
/// Lua may use these hints to preallocate memory for the new table.
pub fn create_table_with_capacity(&self, narr: c_int, nrec: c_int) -> Result<Table> {
unsafe {
let _sg = StackGuard::new(self.state);
assert_stack(self.state, 4);
protect_lua_closure(self.state, 0, 1, |state| {
ffi::lua_createtable(state, narr, nrec)
})?;
Ok(Table(self.pop_ref()))
}
}
/// Creates a table and fills it with values from an iterator.
pub fn create_table_from<'lua, K, V, I>(&'lua self, cont: I) -> Result<Table<'lua>>
where
@@ -903,8 +957,9 @@ impl Lua {
/// ```
///
/// [`Thread`]: struct.Thread.html
/// [`ThreadStream`]: struct.ThreadStream.html
/// [`AsyncThread`]: struct.AsyncThread.html
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
pub fn create_async_function<'lua, 'callback, A, R, F, FR>(
&'lua self,
func: F,
@@ -947,7 +1002,19 @@ impl Lua {
where
T: 'static + MaybeSend + UserData,
{
unsafe { self.make_userdata(data) }
unsafe { self.make_userdata(UserDataWrapped::new(data)) }
}
/// Create a Lua userdata object from a custom serializable userdata type.
///
/// Requires `feature = "serialize"`
#[cfg(feature = "serialize")]
#[cfg_attr(docsrs, doc(cfg(feature = "serialize")))]
pub fn create_ser_userdata<T>(&self, data: T) -> Result<AnyUserData>
where
T: 'static + MaybeSend + UserData + Serialize,
{
unsafe { self.make_userdata(UserDataWrapped::new_ser(data)) }
}
/// Returns a handle to the global environment.
@@ -1008,6 +1075,7 @@ impl Lua {
///
/// [`scope`]: #method.scope
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
pub fn async_scope<'lua, 'scope, R, F, FR>(
&'lua self,
f: F,
@@ -1274,11 +1342,10 @@ impl Lua {
/// by `Lua::remove_registry_value`.
pub fn expire_registry_values(&self) {
unsafe {
let mut extra = mlua_expect!(self.extra.lock(), "extra is poisoned");
let unref_list = mem::replace(
&mut *mlua_expect!(extra.registry_unref_list.lock(), "unref list poisoned"),
Some(Vec::new()),
);
let extra = mlua_expect!(self.extra.lock(), "extra is poisoned");
let mut unref_list =
mlua_expect!(extra.registry_unref_list.lock(), "unref list poisoned");
let unref_list = mem::replace(&mut *unref_list, Some(Vec::new()));
for id in mlua_expect!(unref_list, "unref list not set") {
ffi::luaL_unref(self.state, ffi::LUA_REGISTRYINDEX, id);
}
@@ -1338,32 +1405,33 @@ impl Lua {
// Uses 2 stack spaces, does not call checkstack
pub(crate) unsafe fn pop_value(&self) -> Value {
match ffi::lua_type(self.state, -1) {
let state = self.state;
match ffi::lua_type(state, -1) {
ffi::LUA_TNIL => {
ffi::lua_pop(self.state, 1);
ffi::lua_pop(state, 1);
Nil
}
ffi::LUA_TBOOLEAN => {
let b = Value::Boolean(ffi::lua_toboolean(self.state, -1) != 0);
ffi::lua_pop(self.state, 1);
let b = Value::Boolean(ffi::lua_toboolean(state, -1) != 0);
ffi::lua_pop(state, 1);
b
}
ffi::LUA_TLIGHTUSERDATA => {
let ud = Value::LightUserData(LightUserData(ffi::lua_touserdata(self.state, -1)));
ffi::lua_pop(self.state, 1);
let ud = Value::LightUserData(LightUserData(ffi::lua_touserdata(state, -1)));
ffi::lua_pop(state, 1);
ud
}
ffi::LUA_TNUMBER => {
if ffi::lua_isinteger(self.state, -1) != 0 {
let i = Value::Integer(ffi::lua_tointeger(self.state, -1));
ffi::lua_pop(self.state, 1);
if ffi::lua_isinteger(state, -1) != 0 {
let i = Value::Integer(ffi::lua_tointeger(state, -1));
ffi::lua_pop(state, 1);
i
} else {
let n = Value::Number(ffi::lua_tonumber(self.state, -1));
ffi::lua_pop(self.state, 1);
let n = Value::Number(ffi::lua_tonumber(state, -1));
ffi::lua_pop(state, 1);
n
}
}
@@ -1375,12 +1443,20 @@ impl Lua {
ffi::LUA_TFUNCTION => Value::Function(Function(self.pop_ref())),
ffi::LUA_TUSERDATA => {
// It should not be possible to interact with userdata types other than custom
// UserData types OR a WrappedError. WrappedPanic should not be here.
if let Some(err) = get_wrapped_error(self.state, -1).as_ref() {
// We must prevent interaction with userdata types other than UserData OR a WrappedError.
// WrappedPanics are automatically resumed.
if let Some(err) = get_wrapped_error(state, -1).as_ref() {
let err = err.clone();
ffi::lua_pop(self.state, 1);
ffi::lua_pop(state, 1);
Value::Error(err)
} else if let Some(panic) = get_gc_userdata::<WrappedPanic>(state, -1).as_mut() {
if let Some(panic) = (*panic).0.take() {
ffi::lua_pop(state, 1);
resume_unwind(panic);
}
// Previously resumed panic?
ffi::lua_pop(state, 1);
Nil
} else {
Value::UserData(AnyUserData(self.pop_ref()))
}
@@ -1438,9 +1514,10 @@ impl Lua {
}
pub(crate) unsafe fn userdata_metatable<T: 'static + UserData>(&self) -> Result<c_int> {
let type_id = TypeId::of::<T>();
if let Some(table_id) = mlua_expect!(self.extra.lock(), "extra is poisoned")
.registered_userdata
.get(&TypeId::of::<T>())
.get(&type_id)
{
return Ok(*table_id);
}
@@ -1469,7 +1546,7 @@ impl Lua {
let no_methods = methods.methods.is_empty();
if no_methods {
init_userdata_metatable::<RefCell<T>>(self.state, -1, None)?;
init_userdata_metatable::<UserDataCell<T>>(self.state, -1, None)?;
} else {
protect_lua_closure(self.state, 0, 1, |state| {
ffi::lua_newtable(state);
@@ -1490,21 +1567,58 @@ impl Lua {
})?;
}
init_userdata_metatable::<RefCell<T>>(self.state, -2, Some(-1))?;
init_userdata_metatable::<UserDataCell<T>>(self.state, -2, Some(-1))?;
ffi::lua_pop(self.state, 1);
}
let ptr = ffi::lua_topointer(self.state, -1);
let id = protect_lua_closure(self.state, 1, 0, |state| {
ffi::luaL_ref(state, ffi::LUA_REGISTRYINDEX)
})?;
mlua_expect!(self.extra.lock(), "extra is poisoned")
.registered_userdata
.insert(TypeId::of::<T>(), id);
let mut extra = mlua_expect!(self.extra.lock(), "extra is poisoned");
extra.registered_userdata.insert(type_id, id);
extra.registered_userdata_mt.insert(ptr as isize);
Ok(id)
}
pub(crate) fn register_userdata_metatable(&self, id: isize) {
let mut extra = mlua_expect!(self.extra.lock(), "extra is poisoned");
extra.registered_userdata_mt.insert(id);
}
pub(crate) fn deregister_userdata_metatable(&self, id: isize) {
let mut extra = mlua_expect!(self.extra.lock(), "extra is poisoned");
extra.registered_userdata_mt.remove(&id);
}
// Pushes a LuaRef value onto the stack, checking that it's a registered
// and not destructed UserData.
// Uses 2 stack spaces, does not call checkstack
#[cfg(feature = "serialize")]
pub(crate) unsafe fn push_userdata_ref(&self, lref: &LuaRef) -> Result<()> {
self.push_ref(lref);
if ffi::lua_getmetatable(self.state, -1) == 0 {
return Err(Error::UserDataTypeMismatch);
}
// Check that userdata is registered
let ptr = ffi::lua_topointer(self.state, -1);
let extra = mlua_expect!(self.extra.lock(), "extra is poisoned");
if extra.registered_userdata_mt.contains(&(ptr as isize)) {
ffi::lua_pop(self.state, 1);
return Ok(());
}
// Maybe userdata was destructed?
get_destructed_userdata_metatable(self.state);
if ffi::lua_rawequal(self.state, -1, -2) != 0 {
ffi::lua_pop(self.state, 2);
return Err(Error::UserDataDestructed);
}
ffi::lua_pop(self.state, 2);
Err(Error::UserDataTypeMismatch)
}
// Creates a Function out of a Callback containing a 'static Fn. This is safe ONLY because the
// Fn is 'static, otherwise it could capture 'callback arguments improperly. Without ATCs, we
// cannot easily deal with the "correct" callback type of:
@@ -1522,12 +1636,13 @@ impl Lua {
{
unsafe extern "C" fn call_callback(state: *mut ffi::lua_State) -> c_int {
callback_error(state, |nargs| {
let func =
get_meta_gc_userdata::<Callback, Callback>(state, ffi::lua_upvalueindex(1));
let lua = get_gc_userdata::<Lua>(state, ffi::lua_upvalueindex(2));
if func.is_null() || lua.is_null() {
if ffi::lua_type(state, ffi::lua_upvalueindex(1)) == ffi::LUA_TNIL
|| ffi::lua_type(state, ffi::lua_upvalueindex(2)) == ffi::LUA_TNIL
{
return Err(Error::CallbackDestructed);
}
let func = get_userdata::<Callback>(state, ffi::lua_upvalueindex(1));
let lua = get_userdata::<Lua>(state, ffi::lua_upvalueindex(2));
if nargs < ffi::LUA_MINSTACK {
check_stack(state, ffi::LUA_MINSTACK - nargs)?;
@@ -1578,18 +1693,22 @@ impl Lua {
'lua: 'callback,
{
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))]
self.load_from_std_lib(StdLib::COROUTINE)?;
{
let libs = mlua_expect!(self.extra.lock(), "extra is poisoned").libs;
if !libs.contains(StdLib::COROUTINE) {
self.load_from_std_lib(StdLib::COROUTINE)?;
}
}
unsafe extern "C" fn call_callback(state: *mut ffi::lua_State) -> c_int {
callback_error(state, |nargs| {
let func = get_meta_gc_userdata::<AsyncCallback, AsyncCallback>(
state,
ffi::lua_upvalueindex(1),
);
let lua = get_gc_userdata::<Lua>(state, ffi::lua_upvalueindex(2));
if func.is_null() || lua.is_null() {
if ffi::lua_type(state, ffi::lua_upvalueindex(1)) == ffi::LUA_TNIL
|| ffi::lua_type(state, ffi::lua_upvalueindex(2)) == ffi::LUA_TNIL
{
return Err(Error::CallbackDestructed);
}
let func = get_userdata::<AsyncCallback>(state, ffi::lua_upvalueindex(1));
let lua = get_userdata::<Lua>(state, ffi::lua_upvalueindex(2));
if nargs < ffi::LUA_MINSTACK {
check_stack(state, ffi::LUA_MINSTACK - nargs)?;
@@ -1616,14 +1735,16 @@ impl Lua {
unsafe extern "C" fn poll_future(state: *mut ffi::lua_State) -> c_int {
callback_error(state, |nargs| {
let fut = get_gc_userdata::<LocalBoxFuture<Result<MultiValue>>>(
if ffi::lua_type(state, ffi::lua_upvalueindex(1)) == ffi::LUA_TNIL
|| ffi::lua_type(state, ffi::lua_upvalueindex(2)) == ffi::LUA_TNIL
{
return Err(Error::CallbackDestructed);
}
let fut = get_userdata::<LocalBoxFuture<Result<MultiValue>>>(
state,
ffi::lua_upvalueindex(1),
);
let lua = get_gc_userdata::<Lua>(state, ffi::lua_upvalueindex(2));
if fut.is_null() || lua.is_null() {
return Err(Error::CallbackDestructed);
}
let lua = get_userdata::<Lua>(state, ffi::lua_upvalueindex(2));
if nargs < ffi::LUA_MINSTACK {
check_stack(state, ffi::LUA_MINSTACK - nargs)?;
@@ -1644,17 +1765,19 @@ impl Lua {
match (*fut).as_mut().poll(&mut ctx) {
Poll::Pending => {
check_stack(state, 6)?;
check_stack(state, 1)?;
ffi::lua_pushboolean(state, 0);
push_gc_userdata(state, AsyncPollPending)?;
Ok(2)
Ok(1)
}
Poll::Ready(results) => {
let results = lua.create_sequence_from(results?)?;
check_stack(state, 2)?;
let results = results?;
let nresults = results.len() as Integer;
let results = lua.create_sequence_from(results)?;
check_stack(state, 3)?;
ffi::lua_pushboolean(state, 1);
lua.push_value(Value::Table(results))?;
Ok(2)
lua.push_value(Value::Integer(nresults))?;
Ok(3)
}
}
})
@@ -1681,22 +1804,31 @@ impl Lua {
env.set("yield", coroutine.get::<_, Function>("yield")?)?;
env.set(
"unpack",
self.create_function(|_, tbl: Table| {
self.create_function(|_, (tbl, len): (Table, Integer)| {
Ok(MultiValue::from_vec(
tbl.sequence_values().collect::<Result<Vec<Value>>>()?,
tbl.raw_sequence_values_by_len(Some(len))
.collect::<Result<Vec<Value>>>()?,
))
})?,
)?;
env.set("pending", unsafe {
let _sg = StackGuard::new(self.state);
check_stack(self.state, 5)?;
push_gc_userdata(self.state, AsyncPollPending)?;
self.pop_value()
})?;
// We set `poll` variable in the env table to be able to destroy upvalues
self.load(
r#"
local poll = get_poll(...)
poll = get_poll(...)
local poll, pending, yield, unpack = poll, pending, yield, unpack
while true do
ready, res = poll()
local ready, res, nres = poll()
if ready then
return unpack(res)
return unpack(res, nres)
end
yield(res)
yield(pending)
end
"#,
)
@@ -1705,7 +1837,7 @@ impl Lua {
.into_function()
}
pub(crate) unsafe fn make_userdata<T>(&self, data: T) -> Result<AnyUserData>
pub(crate) unsafe fn make_userdata<T>(&self, data: UserDataWrapped<T>) -> Result<AnyUserData>
where
T: 'static + UserData,
{
@@ -1713,7 +1845,7 @@ impl Lua {
assert_stack(self.state, 4);
let ud_index = self.userdata_metatable::<T>()?;
push_userdata::<RefCell<T>>(self.state, RefCell::new(data))?;
push_userdata::<UserDataCell<T>>(self.state, RefCell::new(data))?;
ffi::lua_rawgeti(
self.state,
@@ -1802,6 +1934,14 @@ pub struct Chunk<'lua, 'a> {
source: &'a [u8],
name: Option<CString>,
env: Option<Value<'lua>>,
mode: Option<ChunkMode>,
}
/// Represents chunk mode (text or binary).
#[derive(Clone, Copy)]
pub enum ChunkMode {
Text,
Binary,
}
impl<'lua, 'a> Chunk<'lua, 'a> {
@@ -1833,6 +1973,17 @@ impl<'lua, 'a> Chunk<'lua, 'a> {
Ok(self)
}
/// Sets whether the chunk is text or binary (autodetected by default).
///
/// Lua does not check the consistency of binary chunks, therefore this mode is allowed only
/// for instances created with [`Lua::unsafe_new`].
///
/// [`Lua::unsafe_new`]: struct.Lua.html#method.unsafe_new
pub fn set_mode(mut self, mode: ChunkMode) -> Chunk<'lua, 'a> {
self.mode = Some(mode);
self
}
/// Execute this chunk of code.
///
/// This is equivalent to calling the chunk function with no arguments and no return values.
@@ -1849,6 +2000,7 @@ impl<'lua, 'a> Chunk<'lua, 'a> {
///
/// [`Chunk::exec`]: struct.Chunk.html#method.exec
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
pub fn exec_async<'fut>(self) -> LocalBoxFuture<'fut, Result<()>>
where
'lua: 'fut,
@@ -1862,13 +2014,17 @@ impl<'lua, 'a> Chunk<'lua, 'a> {
/// the value that it evaluates to. Otherwise, the chunk is interpreted as a block as normal,
/// and this is equivalent to calling `exec`.
pub fn eval<R: FromLuaMulti<'lua>>(self) -> Result<R> {
// First, try interpreting the lua as an expression by adding
// Bytecode is always interpreted as a statement.
// For source code, first try interpreting the lua as an expression by adding
// "return", then as a statement. This is the same thing the
// actual lua repl does.
if let Ok(function) = self.lua.load_chunk(
if self.source.starts_with(ffi::LUA_SIGNATURE) {
self.call(())
} else if let Ok(function) = self.lua.load_chunk(
&self.expression_source(),
self.name.as_ref(),
self.env.clone(),
self.mode,
) {
function.call(())
} else {
@@ -1884,15 +2040,19 @@ impl<'lua, 'a> Chunk<'lua, 'a> {
///
/// [`Chunk::eval`]: struct.Chunk.html#method.eval
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
pub fn eval_async<'fut, R>(self) -> LocalBoxFuture<'fut, Result<R>>
where
'lua: 'fut,
R: FromLuaMulti<'lua> + 'fut,
{
if let Ok(function) = self.lua.load_chunk(
if self.source.starts_with(ffi::LUA_SIGNATURE) {
self.call_async(())
} else if let Ok(function) = self.lua.load_chunk(
&self.expression_source(),
self.name.as_ref(),
self.env.clone(),
self.mode,
) {
function.call_async(())
} else {
@@ -1915,6 +2075,7 @@ impl<'lua, 'a> Chunk<'lua, 'a> {
///
/// [`Chunk::call`]: struct.Chunk.html#method.call
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
pub fn call_async<'fut, A, R>(self, args: A) -> LocalBoxFuture<'fut, Result<R>>
where
'lua: 'fut,
@@ -1932,7 +2093,7 @@ impl<'lua, 'a> Chunk<'lua, 'a> {
/// This simply compiles the chunk without actually executing it.
pub fn into_function(self) -> Result<Function<'lua>> {
self.lua
.load_chunk(self.source, self.name.as_ref(), self.env)
.load_chunk(self.source, self.name.as_ref(), self.env, self.mode)
}
fn expression_source(&self) -> Vec<u8> {
+1 -1
View File
@@ -34,7 +34,7 @@ impl<'lua, T: ToLua<'lua>> ToLuaMulti<'lua> for T {
impl<'lua, T: FromLua<'lua>> FromLuaMulti<'lua> for T {
fn from_lua_multi(mut values: MultiValue<'lua>, lua: &'lua Lua) -> Result<Self> {
Ok(T::from_lua(values.pop_front().unwrap_or(Nil), lua)?)
T::from_lua(values.pop_front().unwrap_or(Nil), lua)
}
}
+91 -29
View File
@@ -1,16 +1,19 @@
use std::any::Any;
use std::cell::{Cell, RefCell};
use std::cell::{Cell, Ref, RefCell, RefMut};
use std::marker::PhantomData;
use std::mem;
use std::os::raw::c_void;
use std::rc::Rc;
#[cfg(feature = "serialize")]
use serde::Serialize;
use crate::error::{Error, Result};
use crate::ffi;
use crate::function::Function;
use crate::lua::Lua;
use crate::types::{Callback, LuaRef, MaybeSend};
use crate::userdata::{AnyUserData, MetaMethod, UserData, UserDataMethods};
use crate::types::{Callback, LuaRef, MaybeSend, UserDataCell};
use crate::userdata::{AnyUserData, MetaMethod, UserData, UserDataMethods, UserDataWrapped};
use crate::util::{
assert_stack, init_userdata_metatable, protect_lua_closure, push_string, push_userdata,
take_userdata, StackGuard,
@@ -20,9 +23,8 @@ use crate::value::{FromLuaMulti, MultiValue, ToLuaMulti, Value};
#[cfg(feature = "async")]
use {
crate::types::AsyncCallback,
futures_core::future::Future,
futures_core::future::{Future, LocalBoxFuture},
futures_util::future::{self, TryFutureExt},
std::os::raw::c_char,
};
/// Constructed by the [`Lua::scope`] method, allows temporarily creating Lua userdata and
@@ -112,6 +114,7 @@ impl<'lua, 'scope> Scope<'lua, 'scope> {
/// [`Lua::scope`]: struct.Lua.html#method.scope
/// [`Lua::async_scope`]: struct.Lua.html#method.async_scope
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
pub fn create_async_function<'callback, A, R, F, FR>(
&'callback self,
func: F,
@@ -143,6 +146,33 @@ impl<'lua, 'scope> Scope<'lua, 'scope> {
/// [`Lua::create_userdata`]: struct.Lua.html#method.create_userdata
/// [`Lua::scope`]: struct.Lua.html#method.scope
pub fn create_userdata<T>(&self, data: T) -> Result<AnyUserData<'lua>>
where
T: 'static + UserData,
{
self.create_userdata_inner(UserDataWrapped::new(data))
}
/// Create a Lua userdata object from a custom serializable userdata type.
///
/// This is a version of [`Lua::create_ser_userdata`] that creates a userdata which expires on
/// scope drop, and does not require that the userdata type be Send (but still requires that the
/// UserData be 'static).
/// See [`Lua::scope`] for more details.
///
/// Requires `feature = "serialize"`
///
/// [`Lua::create_ser_userdata`]: struct.Lua.html#method.create_ser_userdata
/// [`Lua::scope`]: struct.Lua.html#method.scope
#[cfg(feature = "serialize")]
#[cfg_attr(docsrs, doc(cfg(feature = "serialize")))]
pub fn create_ser_userdata<T>(&self, data: T) -> Result<AnyUserData<'lua>>
where
T: 'static + UserData + Serialize,
{
self.create_userdata_inner(UserDataWrapped::new_ser(data))
}
fn create_userdata_inner<T>(&self, data: UserDataWrapped<T>) -> Result<AnyUserData<'lua>>
where
T: 'static + UserData,
{
@@ -156,7 +186,7 @@ impl<'lua, 'scope> Scope<'lua, 'scope> {
u.lua.push_ref(&u);
// We know the destructor has not run yet because we hold a reference to the
// userdata.
vec![Box::new(take_userdata::<RefCell<T>>(state))]
vec![Box::new(take_userdata::<UserDataCell<T>>(state))]
}));
Ok(u)
}
@@ -189,7 +219,7 @@ impl<'lua, 'scope> Scope<'lua, 'scope> {
where
T: 'scope + UserData,
{
let data = Rc::new(RefCell::new(data));
let data = Rc::new(RefCell::new(UserDataWrapped::new(data)));
// 'callback outliving 'scope is a lie to make the types work out, required due to the
// inability to work with the more correct callback type that is universally quantified over
@@ -198,7 +228,7 @@ impl<'lua, 'scope> Scope<'lua, 'scope> {
// parameters.
fn wrap_method<'scope, 'lua, 'callback: 'scope, T: 'scope>(
scope: &Scope<'lua, 'scope>,
data: Rc<RefCell<T>>,
data: Rc<UserDataCell<T>>,
method: NonStaticMethod<'callback, T>,
) -> Result<Function<'lua>> {
// On methods that actually receive the userdata, we fake a type check on the passed in
@@ -210,20 +240,18 @@ impl<'lua, 'scope> Scope<'lua, 'scope> {
// first argument).
let check_data = data.clone();
let check_ud_type = move |lua: &'callback Lua, value| {
if let Some(value) = value {
if let Value::UserData(u) = value {
unsafe {
assert_stack(lua.state, 1);
lua.push_ref(&u.0);
ffi::lua_getuservalue(lua.state, -1);
#[cfg(any(feature = "lua52", feature = "lua51", feature = "luajit"))]
{
ffi::lua_rawgeti(lua.state, -1, 1);
ffi::lua_remove(lua.state, -2);
}
return ffi::lua_touserdata(lua.state, -1)
== check_data.as_ptr() as *mut c_void;
if let Some(Value::UserData(ud)) = value {
unsafe {
assert_stack(lua.state, 1);
lua.push_ref(&ud.0);
ffi::lua_getuservalue(lua.state, -1);
#[cfg(any(feature = "lua52", feature = "lua51", feature = "luajit"))]
{
ffi::lua_rawgeti(lua.state, -1, 1);
ffi::lua_remove(lua.state, -2);
}
return ffi::lua_touserdata(lua.state, -1)
== check_data.as_ptr() as *mut c_void;
}
}
@@ -236,7 +264,10 @@ impl<'lua, 'scope> Scope<'lua, 'scope> {
if !check_ud_type(lua, args.pop_front()) {
return Err(Error::UserDataTypeMismatch);
}
let data = data.try_borrow().map_err(|_| Error::UserDataBorrowError)?;
let data = data
.try_borrow()
.map(|cell| Ref::map(cell, AsRef::as_ref))
.map_err(|_| Error::UserDataBorrowError)?;
method(lua, &*data, args)
});
unsafe { scope.create_callback(f) }
@@ -252,6 +283,7 @@ impl<'lua, 'scope> Scope<'lua, 'scope> {
.map_err(|_| Error::RecursiveMutCallback)?;
let mut data = data
.try_borrow_mut()
.map(|cell| RefMut::map(cell, AsMut::as_mut))
.map_err(|_| Error::UserDataBorrowMutError)?;
(&mut *method)(lua, &mut *data, args)
});
@@ -280,7 +312,8 @@ impl<'lua, 'scope> Scope<'lua, 'scope> {
let _sg = StackGuard::new(lua.state);
assert_stack(lua.state, 6);
push_userdata(lua.state, ())?;
// We need to wrap dummy userdata because their memory can be accessed by serializer
push_userdata(lua.state, UserDataCell::new(UserDataWrapped::new(())))?;
#[cfg(any(feature = "lua54", feature = "lua53"))]
ffi::lua_pushlightuserdata(lua.state, data.as_ptr() as *mut c_void);
#[cfg(any(feature = "lua52", feature = "lua51", feature = "luajit"))]
@@ -323,9 +356,22 @@ impl<'lua, 'scope> Scope<'lua, 'scope> {
ffi::lua_pop(lua.state, 1);
}
let mt_id = ffi::lua_topointer(lua.state, -1);
ffi::lua_setmetatable(lua.state, -2);
Ok(AnyUserData(lua.pop_ref()))
let ud = AnyUserData(lua.pop_ref());
lua.register_userdata_metatable(mt_id as isize);
self.destructors.borrow_mut().push((ud.0.clone(), |ud| {
let state = ud.lua.state;
assert_stack(state, 2);
ud.lua.push_ref(&ud);
ffi::lua_getmetatable(state, -1);
let mt_id = ffi::lua_topointer(state, -1);
ffi::lua_pop(state, 1);
ud.lua.deregister_userdata_metatable(mt_id as isize);
vec![Box::new(take_userdata::<UserDataCell<()>>(state))]
}));
Ok(ud)
}
}
@@ -387,12 +433,11 @@ impl<'lua, 'scope> Scope<'lua, 'scope> {
#[cfg(any(feature = "lua51", feature = "luajit"))]
ffi::lua_getfenv(state, -1);
// Then, get the get_poll() closure using the corresponding key
let key = "get_poll";
ffi::lua_pushlstring(state, key.as_ptr() as *const c_char, key.len());
// Second, get the `get_poll()` closure using the corresponding key
ffi::lua_pushstring(state, cstr!("get_poll"));
ffi::lua_rawget(state, -2);
// Finally, destroy all upvalues
// Destroy all upvalues
ffi::lua_getupvalue(state, -1, 1);
let ud1 = take_userdata::<AsyncCallback>(state);
ffi::lua_pushnil(state);
@@ -404,8 +449,25 @@ impl<'lua, 'scope> Scope<'lua, 'scope> {
ffi::lua_setupvalue(state, -2, 2);
ffi::lua_pop(state, 1);
let mut data: Vec<Box<dyn Any>> = vec![Box::new(ud1), Box::new(ud2)];
vec![Box::new(ud1), Box::new(ud2)]
// Finally, get polled future and destroy it
ffi::lua_pushstring(state, cstr!("poll"));
if ffi::lua_rawget(state, -2) == ffi::LUA_TFUNCTION {
ffi::lua_getupvalue(state, -1, 1);
let ud3 = take_userdata::<LocalBoxFuture<Result<MultiValue>>>(state);
ffi::lua_pushnil(state);
ffi::lua_setupvalue(state, -2, 1);
data.push(Box::new(ud3));
ffi::lua_getupvalue(state, -1, 2);
let ud4 = take_userdata::<Lua>(state);
ffi::lua_pushnil(state);
ffi::lua_setupvalue(state, -2, 2);
data.push(Box::new(ud4));
}
data
}));
Ok(f)
+329
View File
@@ -0,0 +1,329 @@
use std::string::String as StdString;
use serde::de::{self, IntoDeserializer};
use crate::error::{Error, Result};
use crate::table::{TablePairs, TableSequence};
use crate::value::Value;
/// A struct for deserializing Lua values into Rust values.
pub struct Deserializer<'lua>(pub Value<'lua>);
impl<'lua, 'de> serde::Deserializer<'de> for Deserializer<'lua> {
type Error = Error;
#[inline]
fn deserialize_any<V>(self, visitor: V) -> Result<V::Value>
where
V: de::Visitor<'de>,
{
match self.0 {
Value::Nil => visitor.visit_unit(),
Value::Boolean(b) => visitor.visit_bool(b),
#[allow(clippy::useless_conversion)]
Value::Integer(i) => visitor.visit_i64(i.into()),
#[allow(clippy::useless_conversion)]
Value::Number(n) => visitor.visit_f64(n.into()),
Value::String(s) => match s.to_str() {
Ok(s) => visitor.visit_str(s),
Err(_) => visitor.visit_bytes(s.as_bytes()),
},
Value::Table(ref t) if t.raw_len() > 0 || t.is_array() => self.deserialize_seq(visitor),
Value::Table(_) => self.deserialize_map(visitor),
Value::LightUserData(ud) if ud.0.is_null() => visitor.visit_none(),
Value::Function(_)
| Value::Thread(_)
| Value::UserData(_)
| Value::LightUserData(_)
| Value::Error(_) => Err(de::Error::custom("invalid value type")),
}
}
#[inline]
fn deserialize_option<V>(self, visitor: V) -> Result<V::Value>
where
V: de::Visitor<'de>,
{
match self.0 {
Value::Nil => visitor.visit_none(),
Value::LightUserData(ud) if ud.0.is_null() => visitor.visit_none(),
_ => visitor.visit_some(self),
}
}
#[inline]
fn deserialize_enum<V>(
self,
_name: &str,
_variants: &'static [&'static str],
visitor: V,
) -> Result<V::Value>
where
V: de::Visitor<'de>,
{
let (variant, value) = match self.0 {
Value::Table(value) => {
let mut iter = value.pairs::<StdString, Value>();
let (variant, value) = match iter.next() {
Some(v) => v?,
None => {
return Err(de::Error::invalid_value(
de::Unexpected::Map,
&"map with a single key",
))
}
};
if iter.next().is_some() {
return Err(de::Error::invalid_value(
de::Unexpected::Map,
&"map with a single key",
));
}
(variant, Some(value))
}
Value::String(variant) => (variant.to_str()?.to_owned(), None),
_ => return Err(de::Error::custom("bad enum value")),
};
visitor.visit_enum(EnumDeserializer { variant, value })
}
#[inline]
fn deserialize_seq<V>(self, visitor: V) -> Result<V::Value>
where
V: de::Visitor<'de>,
{
match self.0 {
Value::Table(t) => {
let len = t.raw_len() as usize;
let mut deserializer = SeqDeserializer(t.raw_sequence_values());
let seq = visitor.visit_seq(&mut deserializer)?;
if deserializer.0.count() == 0 {
Ok(seq)
} else {
Err(de::Error::invalid_length(
len,
&"fewer elements in the table",
))
}
}
_ => Err(de::Error::custom("invalid value type")),
}
}
#[inline]
fn deserialize_tuple<V>(self, _len: usize, visitor: V) -> Result<V::Value>
where
V: de::Visitor<'de>,
{
self.deserialize_seq(visitor)
}
#[inline]
fn deserialize_tuple_struct<V>(
self,
_name: &'static str,
_len: usize,
visitor: V,
) -> Result<V::Value>
where
V: de::Visitor<'de>,
{
self.deserialize_seq(visitor)
}
#[inline]
fn deserialize_map<V>(self, visitor: V) -> Result<V::Value>
where
V: de::Visitor<'de>,
{
match self.0 {
Value::Table(t) => {
let mut deserializer = MapDeserializer::new(t.pairs());
let map = visitor.visit_map(&mut deserializer)?;
let count = deserializer.pairs.count();
if count == 0 {
Ok(map)
} else {
Err(de::Error::invalid_length(
deserializer.processed + count,
&"fewer elements in the table",
))
}
}
_ => Err(de::Error::custom("invalid value type")),
}
}
#[inline]
fn deserialize_struct<V>(
self,
_name: &'static str,
_fields: &'static [&'static str],
visitor: V,
) -> Result<V::Value>
where
V: de::Visitor<'de>,
{
self.deserialize_map(visitor)
}
serde::forward_to_deserialize_any! {
bool i8 i16 i32 i64 u8 u16 u32 u64 f32 f64 char str string bytes
byte_buf unit unit_struct newtype_struct
identifier ignored_any
}
}
struct SeqDeserializer<'lua>(TableSequence<'lua, Value<'lua>>);
impl<'lua, 'de> de::SeqAccess<'de> for SeqDeserializer<'lua> {
type Error = Error;
fn next_element_seed<T>(&mut self, seed: T) -> Result<Option<T::Value>>
where
T: de::DeserializeSeed<'de>,
{
match self.0.next() {
Some(value) => seed.deserialize(Deserializer(value?)).map(Some),
None => Ok(None),
}
}
fn size_hint(&self) -> Option<usize> {
match self.0.size_hint() {
(lower, Some(upper)) if lower == upper => Some(upper),
_ => None,
}
}
}
struct MapDeserializer<'lua> {
pairs: TablePairs<'lua, Value<'lua>, Value<'lua>>,
value: Option<Value<'lua>>,
processed: usize,
}
impl<'lua> MapDeserializer<'lua> {
fn new(pairs: TablePairs<'lua, Value<'lua>, Value<'lua>>) -> Self {
MapDeserializer {
pairs,
value: None,
processed: 0,
}
}
}
impl<'lua, 'de> de::MapAccess<'de> for MapDeserializer<'lua> {
type Error = Error;
fn next_key_seed<T>(&mut self, seed: T) -> Result<Option<T::Value>>
where
T: de::DeserializeSeed<'de>,
{
match self.pairs.next() {
Some(item) => {
let (key, value) = item?;
self.processed += 1;
self.value = Some(value);
let key_de = Deserializer(key);
seed.deserialize(key_de).map(Some)
}
None => Ok(None),
}
}
fn next_value_seed<T>(&mut self, seed: T) -> Result<T::Value>
where
T: de::DeserializeSeed<'de>,
{
match self.value.take() {
Some(value) => seed.deserialize(Deserializer(value)),
None => Err(de::Error::custom("value is missing")),
}
}
fn size_hint(&self) -> Option<usize> {
match self.pairs.size_hint() {
(lower, Some(upper)) if lower == upper => Some(upper),
_ => None,
}
}
}
struct EnumDeserializer<'lua> {
variant: StdString,
value: Option<Value<'lua>>,
}
impl<'lua, 'de> de::EnumAccess<'de> for EnumDeserializer<'lua> {
type Error = Error;
type Variant = VariantDeserializer<'lua>;
fn variant_seed<T>(self, seed: T) -> Result<(T::Value, Self::Variant)>
where
T: de::DeserializeSeed<'de>,
{
let variant = self.variant.into_deserializer();
let variant_access = VariantDeserializer { value: self.value };
seed.deserialize(variant).map(|v| (v, variant_access))
}
}
struct VariantDeserializer<'lua> {
value: Option<Value<'lua>>,
}
impl<'lua, 'de> de::VariantAccess<'de> for VariantDeserializer<'lua> {
type Error = Error;
fn unit_variant(self) -> Result<()> {
match self.value {
Some(_) => Err(de::Error::invalid_type(
de::Unexpected::NewtypeVariant,
&"unit variant",
)),
None => Ok(()),
}
}
fn newtype_variant_seed<T>(self, seed: T) -> Result<T::Value>
where
T: de::DeserializeSeed<'de>,
{
match self.value {
Some(value) => seed.deserialize(Deserializer(value)),
None => Err(de::Error::invalid_type(
de::Unexpected::UnitVariant,
&"newtype variant",
)),
}
}
fn tuple_variant<V>(self, _len: usize, visitor: V) -> Result<V::Value>
where
V: de::Visitor<'de>,
{
match self.value {
Some(value) => serde::Deserializer::deserialize_seq(Deserializer(value), visitor),
None => Err(de::Error::invalid_type(
de::Unexpected::UnitVariant,
&"tuple variant",
)),
}
}
fn struct_variant<V>(self, _fields: &'static [&'static str], visitor: V) -> Result<V::Value>
where
V: de::Visitor<'de>,
{
match self.value {
Some(value) => serde::Deserializer::deserialize_map(Deserializer(value), visitor),
None => Err(de::Error::invalid_type(
de::Unexpected::UnitVariant,
&"struct variant",
)),
}
}
}
+200
View File
@@ -0,0 +1,200 @@
//! (De)Serialization support using serde.
use std::os::raw::{c_int, c_void};
use std::ptr;
use serde::{Deserialize, Serialize};
use crate::error::Result;
use crate::ffi;
use crate::lua::Lua;
use crate::table::Table;
use crate::util::{assert_stack, protect_lua, StackGuard};
use crate::value::Value;
pub trait LuaSerdeExt<'lua> {
/// A special value (lightuserdata) to encode/decode optional (none) values.
///
/// Requires `feature = "serialize"`
///
/// # Example
///
/// ```
/// use std::collections::HashMap;
/// use mlua::{Lua, Result, LuaSerdeExt};
///
/// fn main() -> Result<()> {
/// let lua = Lua::new();
/// lua.globals().set("null", lua.null()?)?;
///
/// let val = lua.load(r#"{a = null}"#).eval()?;
/// let map: HashMap<String, Option<String>> = lua.from_value(val)?;
/// assert_eq!(map["a"], None);
///
/// Ok(())
/// }
/// ```
fn null(&'lua self) -> Result<Value<'lua>>;
/// 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.
///
/// Requires `feature = "serialize"`
///
/// # Example
///
/// ```
/// use mlua::{Lua, Result, LuaSerdeExt};
/// use serde_json::Value as JsonValue;
///
/// fn main() -> Result<()> {
/// let lua = Lua::new();
/// lua.globals().set("array_mt", lua.array_metatable()?)?;
///
/// // Encode as an empty array (no sequence part in the lua table)
/// let val = lua.load("setmetatable({a = 5}, array_mt)").eval()?;
/// let j: JsonValue = lua.from_value(val)?;
/// assert_eq!(j.to_string(), "[]");
///
/// // Encode as object
/// let val = lua.load("{a = 5}").eval()?;
/// let j: JsonValue = lua.from_value(val)?;
/// assert_eq!(j.to_string(), r#"{"a":5}"#);
///
/// Ok(())
/// }
/// ```
fn array_metatable(&'lua self) -> Result<Table<'lua>>;
/// Converts `T` into a `Value` instance.
///
/// Requires `feature = "serialize"`
///
/// [`Value`]: enum.Value.html
///
/// # Example
///
/// ```
/// use mlua::{Lua, Result, LuaSerdeExt};
/// use serde::Serialize;
///
/// #[derive(Serialize)]
/// struct User {
/// name: String,
/// age: u8,
/// }
///
/// fn main() -> Result<()> {
/// let lua = Lua::new();
/// let u = User {
/// name: "John Smith".into(),
/// age: 20,
/// };
/// lua.globals().set("user", lua.to_value(&u)?)?;
/// lua.load(r#"
/// assert(user["name"] == "John Smith")
/// assert(user["age"] == 20)
/// "#).exec()
/// }
/// ```
fn to_value<T: Serialize + ?Sized>(&'lua self, t: &T) -> Result<Value<'lua>>;
/// Deserializes a `Value` into any serde deserializable object.
///
/// Requires `feature = "serialize"`
///
/// [`Value`]: enum.Value.html
///
/// # Example
///
/// ```
/// use mlua::{Lua, Result, LuaSerdeExt};
/// use serde::Deserialize;
///
/// #[derive(Deserialize, Debug, PartialEq)]
/// struct User {
/// name: String,
/// age: u8,
/// }
///
/// fn main() -> Result<()> {
/// let lua = Lua::new();
/// let val = lua.load(r#"{name = "John Smith", age = 20}"#).eval()?;
/// let u: User = lua.from_value(val)?;
///
/// assert_eq!(u, User { name: "John Smith".into(), age: 20 });
///
/// Ok(())
/// }
/// ```
fn from_value<T: Deserialize<'lua>>(&'lua self, value: Value<'lua>) -> Result<T>;
}
impl<'lua> LuaSerdeExt<'lua> for Lua {
fn null(&'lua self) -> Result<Value<'lua>> {
unsafe {
let _sg = StackGuard::new(self.state);
assert_stack(self.state, 3);
unsafe extern "C" fn push_null(state: *mut ffi::lua_State) -> c_int {
ffi::lua_pushlightuserdata(state, ptr::null_mut());
1
}
protect_lua(self.state, 0, push_null)?;
Ok(self.pop_value())
}
}
fn array_metatable(&'lua self) -> Result<Table<'lua>> {
unsafe {
let _sg = StackGuard::new(self.state);
assert_stack(self.state, 3);
unsafe extern "C" fn get_array_mt(state: *mut ffi::lua_State) -> c_int {
push_array_metatable(state);
1
}
protect_lua(self.state, 0, get_array_mt)?;
Ok(Table(self.pop_ref()))
}
}
fn to_value<T>(&'lua self, t: &T) -> Result<Value<'lua>>
where
T: Serialize + ?Sized,
{
t.serialize(ser::Serializer(self))
}
fn from_value<T>(&'lua self, value: Value<'lua>) -> Result<T>
where
T: Deserialize<'lua>,
{
T::deserialize(de::Deserializer(value))
}
}
pub(crate) unsafe fn init_metatables(state: *mut ffi::lua_State) {
ffi::lua_pushlightuserdata(
state,
&ARRAY_METATABLE_REGISTRY_KEY as *const u8 as *mut c_void,
);
ffi::lua_newtable(state);
ffi::lua_pushstring(state, cstr!("__metatable"));
ffi::lua_pushboolean(state, 0);
ffi::lua_rawset(state, -3);
ffi::lua_rawset(state, ffi::LUA_REGISTRYINDEX);
}
pub(crate) unsafe fn push_array_metatable(state: *mut ffi::lua_State) {
let key = &ARRAY_METATABLE_REGISTRY_KEY as *const u8 as *mut c_void;
ffi::lua_rawgetp(state, ffi::LUA_REGISTRYINDEX, key);
}
static ARRAY_METATABLE_REGISTRY_KEY: u8 = 0;
pub mod de;
pub mod ser;
+366
View File
@@ -0,0 +1,366 @@
use std::os::raw::c_int;
use serde::{ser, Serialize};
use super::LuaSerdeExt;
use crate::error::{Error, Result};
use crate::ffi;
use crate::lua::Lua;
use crate::string::String;
use crate::table::Table;
use crate::types::Integer;
use crate::util::{assert_stack, protect_lua, StackGuard};
use crate::value::{ToLua, Value};
/// A struct for serializing Rust values into Lua values.
pub struct Serializer<'lua>(pub &'lua Lua);
macro_rules! lua_serialize_number {
($name:ident, $t:ty) => {
#[inline]
fn $name(self, value: $t) -> Result<Value<'lua>> {
value.to_lua(self.0)
}
};
}
impl<'lua> ser::Serializer for Serializer<'lua> {
type Ok = Value<'lua>;
type Error = Error;
// Associated types for keeping track of additional state while serializing
// compound data structures like sequences and maps.
type SerializeSeq = SerializeVec<'lua>;
type SerializeTuple = SerializeVec<'lua>;
type SerializeTupleStruct = SerializeVec<'lua>;
type SerializeTupleVariant = SerializeTupleVariant<'lua>;
type SerializeMap = SerializeMap<'lua>;
type SerializeStruct = SerializeMap<'lua>;
type SerializeStructVariant = SerializeStructVariant<'lua>;
#[inline]
fn serialize_bool(self, value: bool) -> Result<Value<'lua>> {
Ok(Value::Boolean(value))
}
lua_serialize_number!(serialize_i8, i8);
lua_serialize_number!(serialize_u8, u8);
lua_serialize_number!(serialize_i16, i16);
lua_serialize_number!(serialize_u16, u16);
lua_serialize_number!(serialize_i32, i32);
lua_serialize_number!(serialize_u32, u32);
lua_serialize_number!(serialize_i64, i64);
lua_serialize_number!(serialize_u64, u64);
lua_serialize_number!(serialize_f32, f32);
lua_serialize_number!(serialize_f64, f64);
#[inline]
fn serialize_char(self, value: char) -> Result<Value<'lua>> {
self.serialize_str(&value.to_string())
}
#[inline]
fn serialize_str(self, value: &str) -> Result<Value<'lua>> {
self.0.create_string(value).map(Value::String)
}
#[inline]
fn serialize_bytes(self, value: &[u8]) -> Result<Value<'lua>> {
self.0.create_string(value).map(Value::String)
}
#[inline]
fn serialize_none(self) -> Result<Value<'lua>> {
self.0.null()
}
#[inline]
fn serialize_some<T>(self, value: &T) -> Result<Value<'lua>>
where
T: ?Sized + Serialize,
{
value.serialize(self)
}
#[inline]
fn serialize_unit(self) -> Result<Value<'lua>> {
self.0.null()
}
#[inline]
fn serialize_unit_struct(self, _name: &'static str) -> Result<Value<'lua>> {
self.0.null()
}
#[inline]
fn serialize_unit_variant(
self,
_name: &'static str,
_variant_index: u32,
variant: &'static str,
) -> Result<Value<'lua>> {
self.serialize_str(variant)
}
#[inline]
fn serialize_newtype_struct<T>(self, _name: &'static str, value: &T) -> Result<Value<'lua>>
where
T: ?Sized + Serialize,
{
value.serialize(self)
}
#[inline]
fn serialize_newtype_variant<T>(
self,
_name: &'static str,
_variant_index: u32,
variant: &'static str,
value: &T,
) -> Result<Value<'lua>>
where
T: ?Sized + Serialize,
{
let table = self.0.create_table()?;
let variant = self.0.create_string(variant)?;
let value = self.0.to_value(value)?;
table.raw_set(variant, value)?;
Ok(Value::Table(table))
}
#[inline]
fn serialize_seq(self, len: Option<usize>) -> Result<Self::SerializeSeq> {
let len = len.unwrap_or(0) as c_int;
let table = self.0.create_table_with_capacity(len, 0)?;
table.set_metatable(Some(self.0.array_metatable()?));
Ok(SerializeVec { table })
}
#[inline]
fn serialize_tuple(self, len: usize) -> Result<Self::SerializeTuple> {
self.serialize_seq(Some(len))
}
#[inline]
fn serialize_tuple_struct(
self,
_name: &'static str,
len: usize,
) -> Result<Self::SerializeTupleStruct> {
self.serialize_seq(Some(len))
}
#[inline]
fn serialize_tuple_variant(
self,
_name: &'static str,
_variant_index: u32,
variant: &'static str,
_len: usize,
) -> Result<Self::SerializeTupleVariant> {
let name = self.0.create_string(variant)?;
let table = self.0.create_table()?;
Ok(SerializeTupleVariant { name, table })
}
#[inline]
fn serialize_map(self, len: Option<usize>) -> Result<Self::SerializeMap> {
let len = len.unwrap_or(0) as c_int;
Ok(SerializeMap {
key: None,
table: self.0.create_table_with_capacity(0, len)?,
})
}
#[inline]
fn serialize_struct(self, _name: &'static str, len: usize) -> Result<Self::SerializeStruct> {
self.serialize_map(Some(len))
}
#[inline]
fn serialize_struct_variant(
self,
_name: &'static str,
_variant_index: u32,
variant: &'static str,
len: usize,
) -> Result<Self::SerializeStructVariant> {
let name = self.0.create_string(variant)?;
let table = self.0.create_table_with_capacity(0, len as c_int)?;
Ok(SerializeStructVariant { name, table })
}
}
pub struct SerializeVec<'lua> {
table: Table<'lua>,
}
impl<'lua> ser::SerializeSeq for SerializeVec<'lua> {
type Ok = Value<'lua>;
type Error = Error;
fn serialize_element<T>(&mut self, value: &T) -> Result<()>
where
T: ?Sized + Serialize,
{
let lua = self.table.0.lua;
let value = lua.to_value(value)?;
unsafe {
let _sg = StackGuard::new(lua.state);
assert_stack(lua.state, 4);
lua.push_ref(&self.table.0);
lua.push_value(value)?;
unsafe extern "C" fn push_to_table(state: *mut ffi::lua_State) -> c_int {
let len = ffi::lua_rawlen(state, -2) as Integer;
ffi::lua_rawseti(state, -2, len + 1);
1
}
protect_lua(lua.state, 2, push_to_table)
}
}
fn end(self) -> Result<Value<'lua>> {
Ok(Value::Table(self.table))
}
}
impl<'lua> ser::SerializeTuple for SerializeVec<'lua> {
type Ok = Value<'lua>;
type Error = Error;
fn serialize_element<T>(&mut self, value: &T) -> Result<()>
where
T: ?Sized + Serialize,
{
ser::SerializeSeq::serialize_element(self, value)
}
fn end(self) -> Result<Value<'lua>> {
ser::SerializeSeq::end(self)
}
}
impl<'lua> ser::SerializeTupleStruct for SerializeVec<'lua> {
type Ok = Value<'lua>;
type Error = Error;
fn serialize_field<T>(&mut self, value: &T) -> Result<()>
where
T: ?Sized + Serialize,
{
ser::SerializeSeq::serialize_element(self, value)
}
fn end(self) -> Result<Value<'lua>> {
ser::SerializeSeq::end(self)
}
}
pub struct SerializeTupleVariant<'lua> {
name: String<'lua>,
table: Table<'lua>,
}
impl<'lua> ser::SerializeTupleVariant for SerializeTupleVariant<'lua> {
type Ok = Value<'lua>;
type Error = Error;
fn serialize_field<T>(&mut self, value: &T) -> Result<()>
where
T: ?Sized + Serialize,
{
let lua = self.table.0.lua;
let idx = self.table.raw_len() + 1;
self.table.raw_insert(idx, lua.to_value(value)?)
}
fn end(self) -> Result<Value<'lua>> {
let lua = self.table.0.lua;
let table = lua.create_table()?;
table.raw_set(self.name, self.table)?;
Ok(Value::Table(table))
}
}
pub struct SerializeMap<'lua> {
table: Table<'lua>,
key: Option<Value<'lua>>,
}
impl<'lua> ser::SerializeMap for SerializeMap<'lua> {
type Ok = Value<'lua>;
type Error = Error;
fn serialize_key<T>(&mut self, key: &T) -> Result<()>
where
T: ?Sized + Serialize,
{
let lua = self.table.0.lua;
self.key = Some(lua.to_value(key)?);
Ok(())
}
fn serialize_value<T>(&mut self, value: &T) -> Result<()>
where
T: ?Sized + Serialize,
{
let lua = self.table.0.lua;
let key = mlua_expect!(
self.key.take(),
"serialize_value called before serialize_key"
);
let value = lua.to_value(value)?;
self.table.raw_set(key, value)
}
fn end(self) -> Result<Value<'lua>> {
Ok(Value::Table(self.table))
}
}
impl<'lua> ser::SerializeStruct for SerializeMap<'lua> {
type Ok = Value<'lua>;
type Error = Error;
fn serialize_field<T>(&mut self, key: &'static str, value: &T) -> Result<()>
where
T: ?Sized + Serialize,
{
ser::SerializeMap::serialize_key(self, key)?;
ser::SerializeMap::serialize_value(self, value)
}
fn end(self) -> Result<Value<'lua>> {
ser::SerializeMap::end(self)
}
}
pub struct SerializeStructVariant<'lua> {
name: String<'lua>,
table: Table<'lua>,
}
impl<'lua> ser::SerializeStructVariant for SerializeStructVariant<'lua> {
type Ok = Value<'lua>;
type Error = Error;
fn serialize_field<T>(&mut self, key: &'static str, value: &T) -> Result<()>
where
T: ?Sized + Serialize,
{
let lua = self.table.0.lua;
self.table.raw_set(key, lua.to_value(value)?)?;
Ok(())
}
fn end(self) -> Result<Value<'lua>> {
let lua = self.table.0.lua;
let table = lua.create_table()?;
table.raw_set(self.name, self.table)?;
Ok(Value::Table(table))
}
}
+2
View File
@@ -47,6 +47,8 @@ impl StdLib {
/// (unsafe) [`debug`](https://www.lua.org/manual/5.3/manual.html#6.10) library
pub const DEBUG: StdLib = StdLib(1 << 31);
/// No libraries
pub const NONE: StdLib = StdLib(0);
/// (unsafe) All standard libraries
pub const ALL: StdLib = StdLib(u32::MAX);
/// The safe subset of the standard libraries
+19
View File
@@ -1,5 +1,11 @@
use std::{slice, str};
#[cfg(feature = "serialize")]
use {
serde::ser::{Serialize, Serializer},
std::result::Result as StdResult,
};
use crate::error::{Error, Result};
use crate::ffi;
use crate::types::LuaRef;
@@ -105,3 +111,16 @@ where
self.as_bytes() == other.as_ref()
}
}
#[cfg(feature = "serialize")]
impl<'lua> Serialize for String<'lua> {
fn serialize<S>(&self, serializer: S) -> StdResult<S::Ok, S::Error>
where
S: Serializer,
{
match self.to_str() {
Ok(s) => serializer.serialize_str(s),
Err(_) => serializer.serialize_bytes(self.as_bytes()),
}
}
}
+89 -3
View File
@@ -1,6 +1,12 @@
use std::marker::PhantomData;
use std::os::raw::c_int;
#[cfg(feature = "serialize")]
use {
serde::ser::{Serialize, SerializeMap, SerializeSeq, Serializer},
std::result::Result as StdResult,
};
use crate::error::{Error, Result};
use crate::ffi;
use crate::function::Function;
@@ -447,9 +453,56 @@ impl<'lua> Table<'lua> {
TableSequence {
table: self.0,
index: Some(1),
len: None,
raw: false,
_phantom: PhantomData,
}
}
/// Consume this table and return an iterator over all values in the sequence part of the table.
///
/// Unlike the `sequence_values`, does not invoke `__index` metamethod when iterating.
///
/// [`sequence_values`]: #method.sequence_values
pub fn raw_sequence_values<V: FromLua<'lua>>(self) -> TableSequence<'lua, V> {
TableSequence {
table: self.0,
index: Some(1),
len: None,
raw: true,
_phantom: PhantomData,
}
}
#[cfg(any(feature = "async", feature = "serialize"))]
pub(crate) fn raw_sequence_values_by_len<V: FromLua<'lua>>(
self,
len: Option<Integer>,
) -> TableSequence<'lua, V> {
let len = len.unwrap_or_else(|| self.raw_len());
TableSequence {
table: self.0,
index: Some(1),
len: Some(len),
raw: true,
_phantom: PhantomData,
}
}
#[cfg(feature = "serialize")]
pub(crate) fn is_array(&self) -> bool {
let lua = self.0.lua;
unsafe {
let _sg = StackGuard::new(lua.state);
assert_stack(lua.state, 3);
lua.push_ref(&self.0);
if ffi::lua_getmetatable(lua.state, -1) == 0 {
return false;
}
crate::serde::push_array_metatable(lua.state);
ffi::lua_rawequal(lua.state, -1, -2) != 0
}
}
}
impl<'lua> PartialEq for Table<'lua> {
@@ -500,6 +553,7 @@ pub trait TableExt<'lua> {
///
/// This might invoke the `__index` metamethod.
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
fn call_async_method<'fut, K, A, R>(&self, key: K, args: A) -> LocalBoxFuture<'fut, Result<R>>
where
'lua: 'fut,
@@ -514,6 +568,7 @@ pub trait TableExt<'lua> {
///
/// This might invoke the `__index` metamethod.
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
fn call_async_function<'fut, K, A, R>(
&self,
key: K,
@@ -580,6 +635,31 @@ impl<'lua> TableExt<'lua> for Table<'lua> {
}
}
#[cfg(feature = "serialize")]
impl<'lua> Serialize for Table<'lua> {
fn serialize<S>(&self, serializer: S) -> StdResult<S::Ok, S::Error>
where
S: Serializer,
{
let len = self.raw_len() as usize;
if len > 0 || self.is_array() {
let mut seq = serializer.serialize_seq(Some(len))?;
for v in self.clone().raw_sequence_values_by_len::<Value>(None) {
let v = v.map_err(serde::ser::Error::custom)?;
seq.serialize_element(&v)?;
}
return seq.end();
}
let mut map = serializer.serialize_map(None)?;
for kv in self.clone().pairs::<Value, Value>() {
let (k, v) = kv.map_err(serde::ser::Error::custom)?;
map.serialize_entry(&k, &v)?;
}
map.end()
}
}
/// An iterator over the pairs of a Lua table.
///
/// This struct is created by the [`Table::pairs`] method.
@@ -651,6 +731,8 @@ where
pub struct TableSequence<'lua, V> {
table: LuaRef<'lua>,
index: Option<Integer>,
len: Option<Integer>,
raw: bool,
_phantom: PhantomData<V>,
}
@@ -669,9 +751,13 @@ where
assert_stack(lua.state, 5);
lua.push_ref(&self.table);
match protect_lua_closure(lua.state, 1, 1, |state| ffi::lua_geti(state, -1, index))
{
Ok(ffi::LUA_TNIL) => None,
let lua_geti = if self.raw {
ffi::lua_rawgeti
} else {
ffi::lua_geti
};
match protect_lua_closure(lua.state, 1, 1, |state| lua_geti(state, -1, index)) {
Ok(ffi::LUA_TNIL) if index > self.len.unwrap_or(0) => None,
Ok(_) => {
let value = lua.pop_value();
self.index = Some(index + 1);
+2
View File
@@ -52,6 +52,7 @@ pub struct Thread<'lua>(pub(crate) LuaRef<'lua>);
/// [`Future`]: ../futures_core/future/trait.Future.html
/// [`Stream`]: ../futures_core/stream/trait.Stream.html
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
#[derive(Debug)]
pub struct AsyncThread<'lua, R> {
thread: Thread<'lua>,
@@ -218,6 +219,7 @@ impl<'lua> Thread<'lua> {
/// # }
/// ```
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
pub fn into_async<A, R>(self, args: A) -> AsyncThread<'lua, R>
where
A: ToLuaMulti<'lua>,
+3
View File
@@ -10,6 +10,7 @@ use crate::error::Result;
use crate::ffi;
use crate::hook::Debug;
use crate::lua::Lua;
use crate::userdata::UserDataWrapped;
use crate::util::{assert_stack, StackGuard};
use crate::value::MultiValue;
@@ -31,6 +32,8 @@ pub(crate) type AsyncCallback<'lua, 'a> =
pub(crate) type HookCallback = Arc<RefCell<dyn FnMut(&Lua, Debug) -> Result<()>>>;
pub(crate) type UserDataCell<T> = RefCell<UserDataWrapped<T>>;
#[cfg(feature = "send")]
pub trait MaybeSend: Send {}
#[cfg(feature = "send")]
+115 -10
View File
@@ -1,15 +1,21 @@
use std::cell::{Ref, RefCell, RefMut};
use std::cell::{Ref, RefMut};
#[cfg(feature = "async")]
use std::future::Future;
#[cfg(feature = "serialize")]
use {
serde::ser::{self, Serialize, Serializer},
std::result::Result as StdResult,
};
use crate::error::{Error, Result};
use crate::ffi;
use crate::function::Function;
use crate::lua::Lua;
use crate::table::Table;
use crate::types::{LuaRef, MaybeSend};
use crate::util::{assert_stack, get_userdata, StackGuard};
use crate::types::{LuaRef, MaybeSend, UserDataCell};
use crate::util::{assert_stack, get_destructed_userdata_metatable, get_userdata, StackGuard};
use crate::value::{FromLua, FromLuaMulti, ToLua, ToLuaMulti, Value};
/// Kinds of metamethods that can be overridden.
@@ -185,6 +191,7 @@ pub trait UserDataMethods<'lua, T: UserData> {
///
/// [`add_method`]: #method.add_method
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
fn add_async_method<S, A, R, M, MR>(&mut self, name: &S, method: M)
where
T: Clone,
@@ -231,6 +238,7 @@ pub trait UserDataMethods<'lua, T: UserData> {
///
/// [`add_function`]: #method.add_function
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
fn add_async_function<S, A, R, F, FR>(&mut self, name: &S, function: F)
where
T: Clone,
@@ -360,6 +368,71 @@ pub trait UserData: Sized {
fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(_methods: &mut M) {}
}
pub(crate) struct UserDataWrapped<T> {
pub(crate) data: *mut T,
#[cfg(feature = "serialize")]
ser: *mut dyn erased_serde::Serialize,
}
impl<T> Drop for UserDataWrapped<T> {
fn drop(&mut self) {
unsafe {
drop(Box::from_raw(self.data));
#[cfg(feature = "serialize")]
if self.data as *mut () != self.ser as *mut () {
drop(Box::from_raw(self.ser));
}
}
}
}
impl<T> UserDataWrapped<T> {
pub(crate) fn new(data: T) -> Self {
UserDataWrapped {
data: Box::into_raw(Box::new(data)),
#[cfg(feature = "serialize")]
ser: Box::into_raw(Box::new(UserDataSerializeError)),
}
}
#[cfg(feature = "serialize")]
pub(crate) fn new_ser(data: T) -> Self
where
T: 'static + Serialize,
{
let data_raw = Box::into_raw(Box::new(data));
UserDataWrapped {
data: data_raw,
ser: data_raw,
}
}
}
impl<T> AsRef<T> for UserDataWrapped<T> {
fn as_ref(&self) -> &T {
unsafe { &*self.data }
}
}
impl<T> AsMut<T> for UserDataWrapped<T> {
fn as_mut(&mut self) -> &mut T {
unsafe { &mut *self.data }
}
}
#[cfg(feature = "serialize")]
pub(crate) struct UserDataSerializeError;
#[cfg(feature = "serialize")]
impl Serialize for UserDataSerializeError {
fn serialize<S>(&self, _serializer: S) -> StdResult<S::Ok, S::Error>
where
S: Serializer,
{
Err(ser::Error::custom("cannot serialize <userdata>"))
}
}
/// Handle to an internal Lua userdata for any type that implements [`UserData`].
///
/// Similar to `std::any::Any`, this provides an interface for dynamic type checking via the [`is`]
@@ -382,7 +455,7 @@ pub struct AnyUserData<'lua>(pub(crate) LuaRef<'lua>);
impl<'lua> AnyUserData<'lua> {
/// Checks whether the type of this userdata is `T`.
pub fn is<T: 'static + UserData>(&self) -> bool {
match self.inspect(|_: &RefCell<T>| Ok(())) {
match self.inspect(|_: &UserDataCell<T>| Ok(())) {
Ok(()) => true,
Err(Error::UserDataTypeMismatch) => false,
Err(_) => unreachable!(),
@@ -396,7 +469,10 @@ impl<'lua> AnyUserData<'lua> {
/// Returns a `UserDataBorrowError` if the userdata is already mutably borrowed. Returns a
/// `UserDataTypeMismatch` if the userdata is not of type `T`.
pub fn borrow<T: 'static + UserData>(&self) -> Result<Ref<T>> {
self.inspect(|cell| Ok(cell.try_borrow().map_err(|_| Error::UserDataBorrowError)?))
self.inspect(|cell| {
let cell_ref = cell.try_borrow().map_err(|_| Error::UserDataBorrowError)?;
Ok(Ref::map(cell_ref, |x| unsafe { &*x.data }))
})
}
/// Borrow this userdata mutably if it is of type `T`.
@@ -407,9 +483,10 @@ impl<'lua> AnyUserData<'lua> {
/// `UserDataTypeMismatch` if the userdata is not of type `T`.
pub fn borrow_mut<T: 'static + UserData>(&self) -> Result<RefMut<T>> {
self.inspect(|cell| {
Ok(cell
let cell_ref = cell
.try_borrow_mut()
.map_err(|_| Error::UserDataBorrowMutError)?)
.map_err(|_| Error::UserDataBorrowMutError)?;
Ok(RefMut::map(cell_ref, |x| unsafe { &mut *x.data }))
})
}
@@ -515,7 +592,7 @@ impl<'lua> AnyUserData<'lua> {
fn inspect<'a, T, R, F>(&'a self, func: F) -> Result<R>
where
T: 'static + UserData,
F: FnOnce(&'a RefCell<T>) -> Result<R>,
F: FnOnce(&'a UserDataCell<T>) -> Result<R>,
{
unsafe {
let lua = self.0.lua;
@@ -534,9 +611,16 @@ impl<'lua> AnyUserData<'lua> {
);
if ffi::lua_rawequal(lua.state, -1, -2) == 0 {
Err(Error::UserDataTypeMismatch)
// Maybe UserData destructed?
ffi::lua_pop(lua.state, 1);
get_destructed_userdata_metatable(lua.state);
if ffi::lua_rawequal(lua.state, -1, -2) == 1 {
Err(Error::UserDataDestructed)
} else {
Err(Error::UserDataTypeMismatch)
}
} else {
func(&*get_userdata::<RefCell<T>>(lua.state, -3))
func(&*get_userdata::<UserDataCell<T>>(lua.state, -3))
}
}
}
@@ -555,3 +639,24 @@ impl<'lua> AsRef<AnyUserData<'lua>> for AnyUserData<'lua> {
self
}
}
#[cfg(feature = "serialize")]
impl<'lua> Serialize for AnyUserData<'lua> {
fn serialize<S>(&self, serializer: S) -> StdResult<S::Ok, S::Error>
where
S: Serializer,
{
let f = || unsafe {
let lua = self.0.lua;
let _sg = StackGuard::new(lua.state);
assert_stack(lua.state, 2);
lua.push_userdata_ref(&self.0)?;
let ud = &*get_userdata::<UserDataCell<()>>(lua.state, -1);
(*ud.try_borrow().map_err(|_| Error::UserDataBorrowError)?.ser)
.serialize(serializer)
.map_err(|err| Error::SerializeError(err.to_string()))
};
f().map_err(ser::Error::custom)
}
}
+13 -22
View File
@@ -186,7 +186,7 @@ pub unsafe fn pop_error(state: *mut ffi::lua_State, err_code: c_int) -> Error {
if let Some(p) = (*panic).0.take() {
resume_unwind(p);
} else {
mlua_panic!("error during panic handling, panic was resumed twice")
Error::PreviouslyResumedPanic
}
} else {
let err_string = to_string(state, -1).into_owned();
@@ -279,15 +279,11 @@ pub unsafe fn push_meta_gc_userdata<MT: Any, T>(state: *mut ffi::lua_State, t: T
// Uses 2 stack spaces, does not call checkstack
pub unsafe fn get_gc_userdata<T: Any>(state: *mut ffi::lua_State, index: c_int) -> *mut T {
get_meta_gc_userdata::<T, T>(state, index)
}
pub unsafe fn get_meta_gc_userdata<MT: Any, T>(state: *mut ffi::lua_State, index: c_int) -> *mut T {
let ud = ffi::lua_touserdata(state, index) as *mut T;
if ud.is_null() || ffi::lua_getmetatable(state, index) == 0 {
return ptr::null_mut();
}
get_gc_metatable_for::<MT>(state);
get_gc_metatable_for::<T>(state);
let res = ffi::lua_rawequal(state, -1, -2) != 0;
ffi::lua_pop(state, 2);
if !res {
@@ -591,27 +587,22 @@ pub unsafe fn init_error_registry(state: *mut ffi::lua_State) {
Ok(err_buf)
} else if let Some(panic) = get_gc_userdata::<WrappedPanic>(state, -1).as_ref() {
if let Some(ref p) = (*panic).0 {
ffi::lua_pushlightuserdata(
state,
&ERROR_PRINT_BUFFER_KEY as *const u8 as *mut c_void,
);
ffi::lua_rawget(state, ffi::LUA_REGISTRYINDEX);
let err_buf_key = &ERROR_PRINT_BUFFER_KEY as *const u8 as *const c_void;
ffi::lua_rawgetp(state, ffi::LUA_REGISTRYINDEX, err_buf_key);
let err_buf = ffi::lua_touserdata(state, -1) as *mut String;
(*err_buf).clear();
ffi::lua_pop(state, 2);
let error = if let Some(x) = p.downcast_ref::<&str>() {
x.to_string()
} else if let Some(x) = p.downcast_ref::<String>() {
x.to_string()
if let Some(msg) = p.downcast_ref::<&str>() {
let _ = write!(&mut (*err_buf), "{}", msg);
} else if let Some(msg) = p.downcast_ref::<String>() {
let _ = write!(&mut (*err_buf), "{}", msg);
} else {
"panic".to_string()
let _ = write!(&mut (*err_buf), "<panic>");
};
(*err_buf).clear();
let _ = write!(&mut (*err_buf), "{}", error);
Ok(err_buf)
} else {
mlua_panic!("error during panic handling, panic was resumed")
Err(Error::PreviouslyResumedPanic)
}
} else {
// I'm not sure whether this is possible to trigger without bugs in mlua?
@@ -725,7 +716,7 @@ pub unsafe fn init_error_registry(state: *mut ffi::lua_State) {
}
struct WrappedError(pub Error);
struct WrappedPanic(pub Option<Box<dyn Any + Send + 'static>>);
pub(crate) struct WrappedPanic(pub Option<Box<dyn Any + Send + 'static>>);
// Converts the given lua value to a string in a reasonable format without causing a Lua error or
// panicking.
@@ -759,7 +750,7 @@ unsafe fn to_string<'a>(state: *mut ffi::lua_State, index: c_int) -> Cow<'a, str
}
}
unsafe fn get_destructed_userdata_metatable(state: *mut ffi::lua_State) {
pub(crate) unsafe fn get_destructed_userdata_metatable(state: *mut ffi::lua_State) {
ffi::lua_pushlightuserdata(
state,
&DESTRUCTED_USERDATA_METATABLE as *const u8 as *mut c_void,
+31
View File
@@ -1,6 +1,12 @@
use std::iter::{self, FromIterator};
use std::{slice, str, vec};
#[cfg(feature = "serialize")]
use {
serde::ser::{self, Serialize, Serializer},
std::result::Result as StdResult,
};
use crate::error::{Error, Result};
use crate::ffi;
use crate::function::Function;
@@ -110,6 +116,31 @@ impl<'lua> AsRef<Value<'lua>> for Value<'lua> {
}
}
#[cfg(feature = "serialize")]
impl<'lua> Serialize for Value<'lua> {
fn serialize<S>(&self, serializer: S) -> StdResult<S::Ok, S::Error>
where
S: Serializer,
{
match self {
Value::Nil => serializer.serialize_unit(),
Value::Boolean(b) => serializer.serialize_bool(*b),
#[allow(clippy::useless_conversion)]
Value::Integer(i) => serializer.serialize_i64((*i).into()),
#[allow(clippy::useless_conversion)]
Value::Number(n) => serializer.serialize_f64((*n).into()),
Value::String(s) => s.serialize(serializer),
Value::Table(t) => t.serialize(serializer),
Value::UserData(ud) => ud.serialize(serializer),
Value::LightUserData(ud) if ud.0.is_null() => serializer.serialize_none(),
Value::Error(_) | Value::LightUserData(_) | Value::Function(_) | Value::Thread(_) => {
let msg = format!("cannot serialize <{}>", self.type_name());
Err(ser::Error::custom(msg))
}
}
}
}
/// Trait for types convertible to `Value`.
pub trait ToLua<'lua> {
/// Performs the conversion.
+36 -12
View File
@@ -1,15 +1,4 @@
#![cfg(feature = "async")]
#![cfg_attr(
all(feature = "luajit", target_os = "macos", target_arch = "x86_64"),
feature(link_args)
)]
#[cfg_attr(
all(feature = "luajit", target_os = "macos", target_arch = "x86_64"),
link_args = "-pagezero_size 10000 -image_base 100000000",
allow(unused_attributes)
)]
extern "system" {}
use std::cell::Cell;
use std::rc::Rc;
@@ -22,7 +11,9 @@ use std::time::Duration;
use futures_timer::Delay;
use futures_util::stream::TryStreamExt;
use mlua::{Error, Function, Lua, Result, Table, TableExt, UserData, UserDataMethods};
use mlua::{
Error, Function, Lua, Result, Table, TableExt, Thread, UserData, UserDataMethods, Value,
};
#[tokio::test]
async fn test_async_function() -> Result<()> {
@@ -134,6 +125,24 @@ async fn test_async_handle_yield() -> Result<()> {
Ok(())
}
#[tokio::test]
async fn test_async_multi_return_nil() -> Result<()> {
let lua = Lua::new();
lua.globals().set(
"func",
lua.create_async_function(|_, _: ()| async { Ok((Option::<String>::None, "error")) })?,
)?;
lua.load(
r#"
local ok, err = func()
assert(err == "error")
"#,
)
.exec_async()
.await
}
#[tokio::test]
async fn test_async_return_async_closure() -> Result<()> {
let lua = Lua::new();
@@ -332,11 +341,18 @@ async fn test_async_scope() -> Result<()> {
let _ = f.call_async::<u64, ()>(10).await?;
assert_eq!(Rc::strong_count(rc), 1);
// Create future in partialy polled state (Poll::Pending)
let g = lua.create_thread(f)?;
g.resume::<u64, ()>(10)?;
lua.globals().set("g", g)?;
assert_eq!(Rc::strong_count(rc), 2);
Ok(())
});
assert_eq!(Rc::strong_count(rc), 1);
let _ = fut.await?;
assert_eq!(Rc::strong_count(rc), 1);
match lua
.globals()
@@ -351,6 +367,14 @@ async fn test_async_scope() -> Result<()> {
r => panic!("improper return for destructed function: {:?}", r),
};
match lua.globals().get::<_, Thread>("g")?.resume::<_, Value>(()) {
Err(Error::CallbackError { ref cause, .. }) => match cause.as_ref() {
Error::CallbackDestructed => {}
e => panic!("expected `CallbackDestructed` error cause, got {:?}", e),
},
r => panic!("improper return for destructed function: {:?}", r),
};
Ok(())
}
-12
View File
@@ -1,15 +1,3 @@
#![cfg_attr(
all(feature = "luajit", target_os = "macos", target_arch = "x86_64"),
feature(link_args)
)]
#[cfg_attr(
all(feature = "luajit", target_os = "macos", target_arch = "x86_64"),
link_args = "-pagezero_size 10000 -image_base 100000000",
allow(unused_attributes)
)]
extern "system" {}
use bstr::{BStr, BString};
use mlua::{Lua, Result};
@@ -22,14 +22,14 @@ note: ...so that the types are compatible
13 | | Ok(())
14 | | });
| |_____________^
= note: expected `main::MyUserData<'_>`
found `main::MyUserData<'a>`
= note: expected `(MyUserData<'_>,)`
found `(MyUserData<'a>,)`
note: but, the lifetime must be valid for the lifetime `'lua` as defined on the method body at 10:24...
--> $DIR/async_nonstatic_userdata.rs:10:24
|
10 | fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) {
| ^^^^
note: ...so that the type `impl std::future::Future` will meet its required lifetime bounds
note: ...so that the type `impl Future` will meet its required lifetime bounds
--> $DIR/async_nonstatic_userdata.rs:11:21
|
11 | methods.add_async_method("print", |_, data, ()| async move {
+12 -7
View File
@@ -1,11 +1,16 @@
error[E0277]: the type `std::cell::UnsafeCell<()>` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary
error[E0277]: the type `UnsafeCell<()>` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary
--> $DIR/lua_norefunwindsafe.rs:7:5
|
7 | catch_unwind(|| lua.create_table().unwrap());
| ^^^^^^^^^^^^ `std::cell::UnsafeCell<()>` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary
| ^^^^^^^^^^^^ `UnsafeCell<()>` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary
|
= help: within `mlua::lua::Lua`, the trait `std::panic::RefUnwindSafe` is not implemented for `std::cell::UnsafeCell<()>`
= note: required because it appears within the type `std::marker::PhantomData<std::cell::UnsafeCell<()>>`
= note: required because it appears within the type `mlua::lua::Lua`
= note: required because of the requirements on the impl of `std::panic::UnwindSafe` for `&mlua::lua::Lua`
= note: required because it appears within the type `[closure@$DIR/tests/compile/lua_norefunwindsafe.rs:7:18: 7:48 lua:&mlua::lua::Lua]`
::: $RUST/std/src/panic.rs
|
| pub fn catch_unwind<F: FnOnce() -> R + UnwindSafe, R>(f: F) -> Result<R> {
| ---------- required by this bound in `catch_unwind`
|
= help: within `Lua`, the trait `RefUnwindSafe` is not implemented for `UnsafeCell<()>`
= note: required because it appears within the type `PhantomData<UnsafeCell<()>>`
= note: required because it appears within the type `Lua`
= note: required because of the requirements on the impl of `UnwindSafe` for `&Lua`
= note: required because it appears within the type `[closure@$DIR/tests/compile/lua_norefunwindsafe.rs:7:18: 7:48]`
+6 -6
View File
@@ -1,14 +1,14 @@
error[E0277]: `std::rc::Rc<std::cell::Cell<i32>>` cannot be sent between threads safely
error[E0277]: `Rc<Cell<i32>>` cannot be sent between threads safely
--> $DIR/non_send.rs:11:9
|
11 | lua.create_function(move |_, ()| {
| _________^^^^^^^^^^^^^^^_-
| | |
| | `std::rc::Rc<std::cell::Cell<i32>>` cannot be sent between threads safely
| | `Rc<Cell<i32>>` cannot be sent between threads safely
12 | | Ok(data.get())
13 | | })?
| |_____- within this `[closure@$DIR/tests/compile/non_send.rs:11:25: 13:6 data:std::rc::Rc<std::cell::Cell<i32>>]`
| |_____- within this `[closure@$DIR/tests/compile/non_send.rs:11:25: 13:6]`
|
= help: within `[closure@$DIR/tests/compile/non_send.rs:11:25: 13:6 data:std::rc::Rc<std::cell::Cell<i32>>]`, the trait `std::marker::Send` is not implemented for `std::rc::Rc<std::cell::Cell<i32>>`
= note: required because it appears within the type `[closure@$DIR/tests/compile/non_send.rs:11:25: 13:6 data:std::rc::Rc<std::cell::Cell<i32>>]`
= note: required because of the requirements on the impl of `mlua::types::MaybeSend` for `[closure@$DIR/tests/compile/non_send.rs:11:25: 13:6 data:std::rc::Rc<std::cell::Cell<i32>>]`
= help: within `[closure@$DIR/tests/compile/non_send.rs:11:25: 13:6]`, the trait `Send` is not implemented for `Rc<Cell<i32>>`
= note: required because it appears within the type `[closure@$DIR/tests/compile/non_send.rs:11:25: 13:6]`
= note: required because of the requirements on the impl of `mlua::types::MaybeSend` for `[closure@$DIR/tests/compile/non_send.rs:11:25: 13:6]`
+13 -8
View File
@@ -1,13 +1,18 @@
error[E0277]: the type `std::cell::UnsafeCell<()>` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary
error[E0277]: the type `UnsafeCell<()>` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary
--> $DIR/ref_nounwindsafe.rs:8:5
|
8 | catch_unwind(move || table.set("a", "b").unwrap());
| ^^^^^^^^^^^^ `std::cell::UnsafeCell<()>` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary
| ^^^^^^^^^^^^ `UnsafeCell<()>` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary
|
= help: within `mlua::lua::Lua`, the trait `std::panic::RefUnwindSafe` is not implemented for `std::cell::UnsafeCell<()>`
= note: required because it appears within the type `std::marker::PhantomData<std::cell::UnsafeCell<()>>`
= note: required because it appears within the type `mlua::lua::Lua`
= note: required because of the requirements on the impl of `std::panic::UnwindSafe` for `&mlua::lua::Lua`
::: $RUST/std/src/panic.rs
|
| pub fn catch_unwind<F: FnOnce() -> R + UnwindSafe, R>(f: F) -> Result<R> {
| ---------- required by this bound in `catch_unwind`
|
= help: within `Lua`, the trait `RefUnwindSafe` is not implemented for `UnsafeCell<()>`
= note: required because it appears within the type `PhantomData<UnsafeCell<()>>`
= note: required because it appears within the type `Lua`
= note: required because of the requirements on the impl of `UnwindSafe` for `&Lua`
= note: required because it appears within the type `mlua::types::LuaRef<'_>`
= note: required because it appears within the type `mlua::table::Table<'_>`
= note: required because it appears within the type `[closure@$DIR/tests/compile/ref_nounwindsafe.rs:8:18: 8:54 table:mlua::table::Table<'_>]`
= note: required because it appears within the type `LuaTable<'_>`
= note: required because it appears within the type `[closure@$DIR/tests/compile/ref_nounwindsafe.rs:8:18: 8:54]`
+22 -41
View File
@@ -1,45 +1,26 @@
error[E0495]: cannot infer an appropriate lifetime for autoref due to conflicting requirements
--> $DIR/scope_callback_capture.rs:8:14
|
8 | .create_function_mut(move |_, t: Table| {
| ^^^^^^^^^^^^^^^^^^^
|
note: first, the lifetime cannot outlive the anonymous lifetime #2 defined on the body at 5:15...
--> $DIR/scope_callback_capture.rs:5:15
|
5 | lua.scope(|scope| {
| _______________^
6 | | let mut inner: Option<Table> = None;
7 | | let f = scope
8 | | .create_function_mut(move |_, t: Table| {
... |
16 | | Ok(())
17 | | });
| |_____^
note: ...so that reference does not outlive borrowed content
warning: unused variable: `old`
--> $DIR/scope_callback_capture.rs:9:29
|
9 | if let Some(old) = inner.take() {
| ^^^ help: if this is intentional, prefix it with an underscore: `_old`
|
= note: `#[warn(unused_variables)]` on by default
error[E0521]: borrowed data escapes outside of closure
--> $DIR/scope_callback_capture.rs:7:17
|
7 | let f = scope
| ^^^^^
note: but, the lifetime must be valid for the method call at 5:5...
--> $DIR/scope_callback_capture.rs:5:5
|
5 | / lua.scope(|scope| {
6 | | let mut inner: Option<Table> = None;
7 | | let f = scope
5 | lua.scope(|scope| {
| -----
| |
| `scope` declared here, outside of the closure body
| `scope` is a reference that is only valid in the closure body
6 | let mut inner: Option<Table> = None;
7 | let f = scope
| _________________^
8 | | .create_function_mut(move |_, t: Table| {
9 | | if let Some(old) = inner.take() {
10 | | // Access old callback `Lua`.
... |
16 | | Ok(())
17 | | });
| |______^
note: ...so that a type/lifetime parameter is in scope here
--> $DIR/scope_callback_capture.rs:5:5
|
5 | / lua.scope(|scope| {
6 | | let mut inner: Option<Table> = None;
7 | | let f = scope
8 | | .create_function_mut(move |_, t: Table| {
... |
16 | | Ok(())
17 | | });
| |______^
13 | | Ok(())
14 | | })?;
| |______________^ `scope` escapes the closure body here
+37 -40
View File
@@ -1,45 +1,42 @@
error[E0495]: cannot infer an appropriate lifetime for autoref due to conflicting requirements
--> $DIR/scope_callback_inner.rs:8:14
|
8 | .create_function_mut(|_, t: Table| {
| ^^^^^^^^^^^^^^^^^^^
|
note: first, the lifetime cannot outlive the anonymous lifetime #2 defined on the body at 5:15...
--> $DIR/scope_callback_inner.rs:5:15
|
5 | lua.scope(|scope| {
| _______________^
6 | | let mut inner: Option<Table> = None;
7 | | let f = scope
8 | | .create_function_mut(|_, t: Table| {
... |
13 | | Ok(())
14 | | });
| |_____^
note: ...so that reference does not outlive borrowed content
error[E0521]: borrowed data escapes outside of closure
--> $DIR/scope_callback_inner.rs:7:17
|
7 | let f = scope
| ^^^^^
note: but, the lifetime must be valid for the method call at 5:5...
--> $DIR/scope_callback_inner.rs:5:5
|
5 | / lua.scope(|scope| {
6 | | let mut inner: Option<Table> = None;
7 | | let f = scope
5 | lua.scope(|scope| {
| -----
| |
| `scope` declared here, outside of the closure body
| `scope` is a reference that is only valid in the closure body
6 | let mut inner: Option<Table> = None;
7 | let f = scope
| _________________^
8 | | .create_function_mut(|_, t: Table| {
... |
13 | | Ok(())
14 | | });
| |______^
note: ...so that a type/lifetime parameter is in scope here
--> $DIR/scope_callback_inner.rs:5:5
9 | | inner = Some(t);
10 | | Ok(())
11 | | })?;
| |______________^ `scope` escapes the closure body here
error[E0373]: closure may outlive the current function, but it borrows `inner`, which is owned by the current function
--> $DIR/scope_callback_inner.rs:8:34
|
5 | / lua.scope(|scope| {
6 | | let mut inner: Option<Table> = None;
7 | | let f = scope
5 | lua.scope(|scope| {
| ----- has type `&Scope<'_, '2>`
...
8 | .create_function_mut(|_, t: Table| {
| ^^^^^^^^^^^^^ may outlive borrowed value `inner`
9 | inner = Some(t);
| ----- `inner` is borrowed here
|
note: function requires argument type to outlive `'2`
--> $DIR/scope_callback_inner.rs:7:17
|
7 | let f = scope
| _________________^
8 | | .create_function_mut(|_, t: Table| {
... |
13 | | Ok(())
14 | | });
| |______^
9 | | inner = Some(t);
10 | | Ok(())
11 | | })?;
| |______________^
help: to force the closure to take ownership of `inner` (and any other referenced variables), use the `move` keyword
|
8 | .create_function_mut(move |_, t: Table| {
| ^^^^^^^^^^^^^^^^^^
+30 -11
View File
@@ -1,11 +1,30 @@
error: borrowed data cannot be stored outside of its closure
--> $DIR/scope_callback_outer.rs:7:17
|
5 | let mut outer: Option<Table> = None;
| --------- ...so that variable is valid at time of its declaration
6 | lua.scope(|scope| {
| ------- borrowed data cannot outlive this closure
7 | let f = scope
| ^^^^^ cannot be stored outside of its closure
8 | .create_function_mut(|_, t: Table| {
| ------------------- cannot infer an appropriate lifetime...
error[E0521]: borrowed data escapes outside of closure
--> $DIR/scope_callback_outer.rs:7:17
|
6 | lua.scope(|scope| {
| -----
| |
| `scope` declared here, outside of the closure body
| `scope` is a reference that is only valid in the closure body
7 | let f = scope
| _________________^
8 | | .create_function_mut(|_, t: Table| {
9 | | outer = Some(t);
10 | | Ok(())
11 | | })?;
| |______________^ `scope` escapes the closure body here
error[E0597]: `outer` does not live long enough
--> $DIR/scope_callback_outer.rs:9:17
|
6 | lua.scope(|scope| {
| ------- value captured here
...
9 | outer = Some(t);
| ^^^^^ borrowed value does not live long enough
...
15 | }
| -
| |
| `outer` dropped here while still borrowed
| borrow might be used here, when `outer` is dropped and runs the destructor for type `Option<LuaTable<'_>>`
+1 -1
View File
@@ -2,7 +2,7 @@ error[E0373]: closure may outlive the current function, but it borrows `test`, w
--> $DIR/scope_invariance.rs:14:38
|
9 | lua.scope(|scope| {
| ----- has type `&mlua::scope::Scope<'_, '1>`
| ----- has type `&Scope<'_, '1>`
...
14 | .create_function_mut(|_, ()| {
| ^^^^^^^ may outlive borrowed value `test`
+1 -1
View File
@@ -2,7 +2,7 @@ error[E0597]: `ibad` does not live long enough
--> $DIR/scope_userdata_borrow.rs:15:56
|
11 | lua.scope(|scope| {
| ----- has type `&mlua::scope::Scope<'_, '1>`
| ----- has type `&Scope<'_, '1>`
...
15 | scope.create_nonstatic_userdata(MyUserData(&ibad)).unwrap();
| -------------------------------------------^^^^^--
+14 -12
View File
@@ -1,15 +1,3 @@
#![cfg_attr(
all(feature = "luajit", target_os = "macos", target_arch = "x86_64"),
feature(link_args)
)]
#[cfg_attr(
all(feature = "luajit", target_os = "macos", target_arch = "x86_64"),
link_args = "-pagezero_size 10000 -image_base 100000000",
allow(unused_attributes)
)]
extern "system" {}
use mlua::{Function, Lua, Result, String};
#[test]
@@ -87,3 +75,17 @@ fn test_rust_function() -> Result<()> {
Ok(())
}
#[test]
fn test_dump() -> Result<()> {
let lua = unsafe { Lua::unsafe_new() };
let concat_lua = lua
.load(r#"function(arg1, arg2) return arg1 .. arg2 end"#)
.eval::<Function>()?;
let concat = lua.load(&concat_lua.dump(false)?).into_function()?;
assert_eq!(concat.call::<_, String>(("foo", "bar"))?, "foobar");
Ok(())
}
-12
View File
@@ -1,15 +1,3 @@
#![cfg_attr(
all(feature = "luajit", target_os = "macos", target_arch = "x86_64"),
feature(link_args)
)]
#[cfg_attr(
all(feature = "luajit", target_os = "macos", target_arch = "x86_64"),
link_args = "-pagezero_size 10000 -image_base 100000000",
allow(unused_attributes)
)]
extern "system" {}
use std::cell::RefCell;
use std::ops::Deref;
use std::str;
-12
View File
@@ -1,15 +1,3 @@
#![cfg_attr(
all(feature = "luajit", target_os = "macos", target_arch = "x86_64"),
feature(link_args)
)]
#[cfg_attr(
all(feature = "luajit", target_os = "macos", target_arch = "x86_64"),
link_args = "-pagezero_size 10000 -image_base 100000000",
allow(unused_attributes)
)]
extern "system" {}
use std::sync::Arc;
use mlua::{Lua, Result, UserData};
+51 -16
View File
@@ -1,19 +1,9 @@
#![cfg_attr(
all(feature = "luajit", target_os = "macos", target_arch = "x86_64"),
feature(link_args)
)]
#[cfg_attr(
all(feature = "luajit", target_os = "macos", target_arch = "x86_64"),
link_args = "-pagezero_size 10000 -image_base 100000000",
allow(unused_attributes)
)]
extern "system" {}
use std::cell::Cell;
use std::rc::Rc;
use mlua::{Error, Function, Lua, MetaMethod, Result, String, UserData, UserDataMethods};
use mlua::{
AnyUserData, Error, Function, Lua, MetaMethod, Result, String, UserData, UserDataMethods,
};
#[test]
fn scope_func() -> Result<()> {
@@ -57,17 +47,62 @@ fn scope_drop() -> Result<()> {
lua.scope(|scope| {
lua.globals()
.set("test", scope.create_userdata(MyUserdata(rc.clone()))?)?;
.set("static_ud", scope.create_userdata(MyUserdata(rc.clone()))?)?;
assert_eq!(Rc::strong_count(&rc), 2);
Ok(())
})?;
assert_eq!(Rc::strong_count(&rc), 1);
match lua.load("test:method()").exec() {
Err(Error::CallbackError { .. }) => {}
match lua.load("static_ud:method()").exec() {
Err(Error::CallbackError { ref cause, .. }) => match cause.as_ref() {
Error::CallbackDestructed => {}
e => panic!("expected CallbackDestructed, got {:?}", e),
},
r => panic!("improper return for destructed userdata: {:?}", r),
};
let static_ud = lua.globals().get::<_, AnyUserData>("static_ud")?;
match static_ud.borrow::<MyUserdata>() {
Ok(_) => panic!("borrowed destructed userdata"),
Err(Error::UserDataDestructed) => {}
Err(e) => panic!("expected UserDataDestructed, got {:?}", e),
}
// Check non-static UserData drop
struct MyUserDataRef<'a>(&'a Cell<i64>);
impl<'a> UserData for MyUserDataRef<'a> {
fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) {
methods.add_method("inc", |_, data, ()| {
data.0.set(data.0.get() + 1);
Ok(())
});
}
}
let i = Cell::new(1);
lua.scope(|scope| {
lua.globals().set(
"nonstatic_ud",
scope.create_nonstatic_userdata(MyUserDataRef(&i))?,
)
})?;
match lua.load("nonstatic_ud:inc(1)").exec() {
Err(Error::CallbackError { ref cause, .. }) => match cause.as_ref() {
Error::CallbackDestructed => {}
e => panic!("expected CallbackDestructed, got {:?}", e),
},
r => panic!("improper return for destructed userdata: {:?}", r),
};
let nonstatic_ud = lua.globals().get::<_, AnyUserData>("nonstatic_ud")?;
match nonstatic_ud.borrow::<MyUserDataRef>() {
Ok(_) => panic!("borrowed destructed userdata"),
Err(Error::UserDataDestructed) => {}
Err(e) => panic!("expected UserDataDestructed, got {:?}", e),
}
Ok(())
}
+343
View File
@@ -0,0 +1,343 @@
#![cfg(feature = "serialize")]
use mlua::{Error, Lua, LuaSerdeExt, Result as LuaResult, UserData, Value};
use serde::{Deserialize, Serialize};
#[test]
fn test_serialize() -> Result<(), Box<dyn std::error::Error>> {
#[derive(Serialize)]
struct MyUserData(i64, String);
impl UserData for MyUserData {}
let lua = Lua::new();
let globals = lua.globals();
let ud = lua.create_ser_userdata(MyUserData(123, "test userdata".into()))?;
globals.set("ud", ud)?;
globals.set("null", lua.null()?)?;
let empty_array = lua.create_table()?;
empty_array.set_metatable(Some(lua.array_metatable()?));
globals.set("empty_array", empty_array)?;
let val = lua
.load(
r#"
{
_bool = true,
_integer = 123,
_number = 321.99,
_string = "test string serialization",
_table_arr = {nil, "value 1", nil, "value 2", {}},
_table_map = {["table"] = "map", ["null"] = null},
_bytes = "\240\040\140\040",
_userdata = ud,
_null = null,
_empty_map = {},
_empty_array = empty_array,
}
"#,
)
.eval::<Value>()?;
let json = serde_json::json!({
"_bool": true,
"_integer": 123,
"_number": 321.99,
"_string": "test string serialization",
"_table_arr": [null, "value 1", null, "value 2", {}],
"_table_map": {"table": "map", "null": null},
"_bytes": [240, 40, 140, 40],
"_userdata": [123, "test userdata"],
"_null": null,
"_empty_map": {},
"_empty_array": [],
});
assert_eq!(serde_json::to_value(&val)?, json);
// Test to-from loop
let val = lua.to_value(&json)?;
let expected_json = lua.from_value::<serde_json::Value>(val)?;
assert_eq!(expected_json, json);
Ok(())
}
#[test]
fn test_serialize_in_scope() -> LuaResult<()> {
#[derive(Serialize, Clone)]
struct MyUserData(i64, String);
impl UserData for MyUserData {}
let lua = Lua::new();
lua.scope(|scope| {
let ud = scope.create_ser_userdata(MyUserData(-5, "test userdata".into()))?;
assert_eq!(
serde_json::to_value(&ud).unwrap(),
serde_json::json!((-5, "test userdata"))
);
Ok(())
})?;
lua.scope(|scope| {
let ud = scope.create_ser_userdata(MyUserData(-5, "test userdata".into()))?;
lua.globals().set("ud", ud)
})?;
let val = lua.load("ud").eval::<Value>()?;
match serde_json::to_value(&val) {
Ok(v) => panic!("expected destructed error, got {}", v),
Err(e) if e.to_string().contains("destructed") => {}
Err(e) => panic!("expected destructed error, got {}", e),
}
struct MyUserDataRef<'a>(&'a ());
impl<'a> UserData for MyUserDataRef<'a> {}
lua.scope(|scope| {
let ud = scope.create_nonstatic_userdata(MyUserDataRef(&()))?;
match serde_json::to_value(&ud) {
Ok(v) => panic!("expected serialization error, got {}", v),
Err(serde_json::Error { .. }) => {}
};
Ok(())
})?;
Ok(())
}
#[test]
fn test_serialize_failure() -> Result<(), Box<dyn std::error::Error>> {
#[derive(Serialize)]
struct MyUserData(i64);
impl UserData for MyUserData {}
let lua = Lua::new();
let ud = Value::UserData(lua.create_userdata(MyUserData(123))?);
match serde_json::to_value(&ud) {
Ok(v) => panic!("expected serialization error, got {}", v),
Err(serde_json::Error { .. }) => {}
}
let func = lua.create_function(|_, _: ()| Ok(()))?;
match serde_json::to_value(&Value::Function(func.clone())) {
Ok(v) => panic!("expected serialization error, got {}", v),
Err(serde_json::Error { .. }) => {}
}
let thr = lua.create_thread(func)?;
match serde_json::to_value(&Value::Thread(thr)) {
Ok(v) => panic!("expected serialization error, got {}", v),
Err(serde_json::Error { .. }) => {}
}
Ok(())
}
#[test]
fn test_to_value_struct() -> LuaResult<()> {
let lua = Lua::new();
let globals = lua.globals();
globals.set("null", lua.null()?)?;
#[derive(Serialize)]
struct Test {
name: String,
key: i64,
data: Option<bool>,
}
let test = Test {
name: "alex".to_string(),
key: -16,
data: None,
};
globals.set("value", lua.to_value(&test)?)?;
lua.load(
r#"
assert(value["name"] == "alex")
assert(value["key"] == -16)
assert(value["data"] == null)
"#,
)
.exec()
}
#[test]
fn test_to_value_enum() -> LuaResult<()> {
let lua = Lua::new();
let globals = lua.globals();
globals.set("null", lua.null()?)?;
#[derive(Serialize)]
struct Test {
name: String,
key: i64,
data: Option<bool>,
}
let test = Test {
name: "alex".to_string(),
key: -16,
data: None,
};
globals.set("value", lua.to_value(&test)?)?;
lua.load(
r#"
assert(value["name"] == "alex")
assert(value["key"] == -16)
assert(value["data"] == null)
"#,
)
.exec()?;
#[derive(Serialize)]
enum E {
Unit,
Integer(u32),
Tuple(u32, u32),
Struct { a: u32 },
}
let u = E::Unit;
globals.set("value", lua.to_value(&u)?)?;
lua.load(r#"assert(value == "Unit")"#).exec()?;
let n = E::Integer(1);
globals.set("value", lua.to_value(&n)?)?;
lua.load(r#"assert(value["Integer"] == 1)"#).exec()?;
let t = E::Tuple(1, 2);
globals.set("value", lua.to_value(&t)?)?;
lua.load(
r#"
assert(value["Tuple"][1] == 1)
assert(value["Tuple"][2] == 2)
"#,
)
.exec()?;
let s = E::Struct { a: 1 };
globals.set("value", lua.to_value(&s)?)?;
lua.load(r#"assert(value["Struct"]["a"] == 1)"#).exec()?;
Ok(())
}
#[test]
fn test_from_value_struct() -> Result<(), Box<dyn std::error::Error>> {
let lua = Lua::new();
#[derive(Deserialize, PartialEq, Debug)]
struct Test {
int: u32,
seq: Vec<String>,
map: std::collections::HashMap<i32, i32>,
empty: Vec<()>,
tuple: (u8, u8, u8),
}
let value = lua
.load(
r#"
{
int = 1,
seq = {"a", "b"},
map = {2, [4] = 1},
empty = {},
tuple = {10, 20, 30},
}
"#,
)
.eval::<Value>()?;
let got = lua.from_value(value)?;
assert_eq!(
Test {
int: 1,
seq: vec!["a".into(), "b".into()],
map: vec![(1, 2), (4, 1)].into_iter().collect(),
empty: vec![],
tuple: (10, 20, 30),
},
got
);
Ok(())
}
#[test]
fn test_from_value_enum() -> Result<(), Box<dyn std::error::Error>> {
let lua = Lua::new();
#[derive(Deserialize, PartialEq, Debug)]
enum E {
Unit,
Integer(u32),
Tuple(u32, u32),
Struct { a: u32 },
}
let value = lua.load(r#""Unit""#).eval()?;
let got = lua.from_value(value)?;
assert_eq!(E::Unit, got);
let value = lua.load(r#"{Integer = 1}"#).eval()?;
let got = lua.from_value(value)?;
assert_eq!(E::Integer(1), got);
let value = lua.load(r#"{Tuple = {1, 2}}"#).eval()?;
let got = lua.from_value(value)?;
assert_eq!(E::Tuple(1, 2), got);
let value = lua.load(r#"{Struct = {a = 3}}"#).eval()?;
let got = lua.from_value(value)?;
assert_eq!(E::Struct { a: 3 }, got);
Ok(())
}
#[test]
fn test_from_value_enum_untagged() -> Result<(), Box<dyn std::error::Error>> {
let lua = Lua::new();
lua.globals().set("null", lua.null()?)?;
#[derive(Deserialize, PartialEq, Debug)]
#[serde(untagged)]
enum Eut {
Unit,
Integer(u64),
Tuple(u32, u32),
Struct { a: u32 },
}
let value = lua.load(r#"null"#).eval()?;
let got = lua.from_value(value)?;
assert_eq!(Eut::Unit, got);
let value = lua.load(r#"1"#).eval()?;
let got = lua.from_value(value)?;
assert_eq!(Eut::Integer(1), got);
let value = lua.load(r#"{3, 1}"#).eval()?;
let got = lua.from_value(value)?;
assert_eq!(Eut::Tuple(3, 1), got);
let value = lua.load(r#"{a = 10}"#).eval()?;
let got = lua.from_value(value)?;
assert_eq!(Eut::Struct { a: 10 }, got);
let value = lua.load(r#"{b = 12}"#).eval()?;
match lua.from_value::<Eut>(value) {
Ok(v) => panic!("expected Error::DeserializeError, got {:?}", v),
Err(Error::DeserializeError(_)) => {}
Err(e) => panic!("expected Error::DeserializeError, got {}", e),
}
Ok(())
}
-12
View File
@@ -1,15 +1,3 @@
#![cfg_attr(
all(feature = "luajit", target_os = "macos", target_arch = "x86_64"),
feature(link_args)
)]
#[cfg_attr(
all(feature = "luajit", target_os = "macos", target_arch = "x86_64"),
link_args = "-pagezero_size 10000 -image_base 100000000",
allow(unused_attributes)
)]
extern "system" {}
use std::borrow::Cow;
use mlua::{Lua, Result, String};
-12
View File
@@ -1,15 +1,3 @@
#![cfg_attr(
all(feature = "luajit", target_os = "macos", target_arch = "x86_64"),
feature(link_args)
)]
#[cfg_attr(
all(feature = "luajit", target_os = "macos", target_arch = "x86_64"),
link_args = "-pagezero_size 10000 -image_base 100000000",
allow(unused_attributes)
)]
extern "system" {}
use mlua::{Lua, Nil, Result, Table, TableExt, Value};
#[test]
+149 -63
View File
@@ -1,23 +1,11 @@
#![cfg_attr(
all(feature = "luajit", target_os = "macos", target_arch = "x86_64"),
feature(link_args)
)]
#[cfg_attr(
all(feature = "luajit", target_os = "macos", target_arch = "x86_64"),
link_args = "-pagezero_size 10000 -image_base 100000000",
allow(unused_attributes)
)]
extern "system" {}
use std::iter::FromIterator;
use std::panic::catch_unwind;
use std::panic::{catch_unwind, AssertUnwindSafe};
use std::sync::Arc;
use std::{error, f32, f64, fmt};
use mlua::{
Error, ExternalError, Function, Lua, Nil, Result, StdLib, String, Table, UserData, Value,
Variadic,
ChunkMode, Error, ExternalError, Function, Lua, Nil, Result, StdLib, String, Table, UserData,
Value, Variadic,
};
#[test]
@@ -56,6 +44,37 @@ fn test_safety() -> Result<()> {
Ok(_) => panic!("expected RuntimeError, got no error"),
}
match lua.load("1 + 1").set_mode(ChunkMode::Binary).exec() {
Err(Error::SafetyError(msg)) => {
assert!(msg.contains("binary chunks are disabled in safe mode"))
}
Err(e) => panic!("expected SafetyError, got {:?}", e),
Ok(_) => panic!("expected SafetyError, got no error"),
}
let bytecode = lua.load("return 1 + 1").into_function()?.dump(true)?;
match lua.load(&bytecode).exec() {
Err(Error::SafetyError(msg)) => {
assert!(msg.contains("binary chunks are disabled in safe mode"))
}
Err(e) => panic!("expected SafetyError, got {:?}", e),
Ok(_) => panic!("expected SafetyError, got no error"),
}
drop(lua);
// Test safety rules after dynamically loading `package` library
let lua = Lua::new_with(StdLib::NONE)?;
assert!(lua.globals().get::<_, Option<Value>>("require")?.is_none());
lua.load_from_std_lib(StdLib::PACKAGE)?;
match lua.load(r#"package.loadlib()"#).exec() {
Err(Error::CallbackError { ref cause, .. }) => match cause.as_ref() {
Error::SafetyError(_) => {}
e => panic!("expected SafetyError cause, got {:?}", e),
},
Err(e) => panic!("expected CallbackError, got {:?}", e),
Ok(_) => panic!("expected CallbackError, got no error"),
};
Ok(())
}
@@ -127,6 +146,41 @@ fn test_eval() -> Result<()> {
Ok(())
}
#[test]
fn test_load_mode() -> Result<()> {
let lua = unsafe { Lua::unsafe_new() };
assert_eq!(
lua.load("1 + 1").set_mode(ChunkMode::Text).eval::<i32>()?,
2
);
match lua.load("1 + 1").set_mode(ChunkMode::Binary).exec() {
Ok(_) => panic!("expected SyntaxError, got no error"),
Err(Error::SyntaxError { message: msg, .. }) => {
assert!(msg.contains("attempt to load a text chunk"))
}
Err(e) => panic!("expected SyntaxError, got {:?}", e),
};
let bytecode = lua.load("return 1 + 1").into_function()?.dump(true)?;
assert_eq!(lua.load(&bytecode).eval::<i32>()?, 2);
assert_eq!(
lua.load(&bytecode)
.set_mode(ChunkMode::Binary)
.eval::<i32>()?,
2
);
match lua.load(&bytecode).set_mode(ChunkMode::Text).exec() {
Ok(_) => panic!("expected SyntaxError, got no error"),
Err(Error::SyntaxError { message: msg, .. }) => {
assert!(msg.contains("attempt to load a binary chunk"))
}
Err(e) => panic!("expected SyntaxError, got {:?}", e),
};
Ok(())
}
#[test]
fn test_lua_multi() -> Result<()> {
let lua = Lua::new();
@@ -318,62 +372,92 @@ fn test_error() -> Result<()> {
assert!(understand_recursion.call::<_, ()>(()).is_err());
match catch_unwind(|| -> Result<()> {
let lua = Lua::new();
let globals = lua.globals();
Ok(())
}
#[test]
fn test_panic() -> Result<()> {
fn make_lua() -> Result<Lua> {
let lua = Lua::new();
let rust_panic_function =
lua.create_function(|_, ()| -> Result<()> { panic!("rust panic") })?;
lua.globals()
.set("rust_panic_function", rust_panic_function)?;
Ok(lua)
}
// Test triggerting Lua error passing Rust panic (must be resumed)
{
let lua = make_lua()?;
match catch_unwind(AssertUnwindSafe(|| -> Result<()> {
lua.load(
r#"
_, err = pcall(rust_panic_function)
error(err)
"#,
)
.exec()
})) {
Ok(Ok(_)) => panic!("no panic was detected"),
Ok(Err(e)) => panic!("error during panic test {:?}", e),
Err(p) => assert!(*p.downcast::<&str>().unwrap() == "rust panic"),
};
// Trigger same panic again
match lua.load("error(err)").exec() {
Ok(_) => panic!("no error was detected"),
Err(Error::PreviouslyResumedPanic) => {}
Err(e) => panic!("expected PreviouslyResumedPanic, got {:?}", e),
}
}
// Test returning Rust panic (must be resumed)
{
let lua = make_lua()?;
match catch_unwind(AssertUnwindSafe(|| -> Result<()> {
let _catched_panic = lua
.load(
r#"
-- Set global
_, err = pcall(rust_panic_function)
return err
"#,
)
.eval::<Value>()?;
Ok(())
})) {
Ok(_) => panic!("no panic was detected"),
Err(_) => {}
};
assert!(lua.globals().get::<_, Value>("err")? == Value::Nil);
match lua.load("tostring(err)").exec() {
Ok(_) => panic!("no error was detected"),
Err(Error::CallbackError { ref cause, .. }) => match cause.as_ref() {
Error::PreviouslyResumedPanic => {}
e => panic!("expected PreviouslyResumedPanic, got {:?}", e),
},
Err(e) => panic!("expected CallbackError, got {:?}", e),
}
}
// Test representing rust panic as a string
match catch_unwind(|| -> Result<()> {
let lua = make_lua()?;
lua.load(
r#"
function rust_panic()
local _, err = pcall(function () rust_panic_function() end)
if err ~= nil then
error(err)
end
end
local _, err = pcall(rust_panic_function)
error(tostring(err))
"#,
)
.exec()?;
let rust_panic_function =
lua.create_function(|_, ()| -> Result<()> { panic!("test_panic") })?;
globals.set("rust_panic_function", rust_panic_function)?;
let rust_panic = globals.get::<_, Function>("rust_panic")?;
rust_panic.call::<_, ()>(())
}) {
Ok(Ok(_)) => panic!("no panic was detected"),
Ok(Err(e)) => panic!("error during panic test {:?}", e),
Err(p) => assert!(*p.downcast::<&str>().unwrap() == "test_panic"),
};
match catch_unwind(|| -> Result<()> {
let lua = Lua::new();
let globals = lua.globals();
lua.load(
r#"
function rust_panic()
local _, err = pcall(function () rust_panic_function() end)
if err ~= nil then
error(tostring(err))
end
end
"#,
)
.exec()?;
let rust_panic_function =
lua.create_function(|_, ()| -> Result<()> { panic!("test_panic") })?;
globals.set("rust_panic_function", rust_panic_function)?;
let rust_panic = globals.get::<_, Function>("rust_panic")?;
rust_panic.call::<_, ()>(())
.exec()
}) {
Ok(Ok(_)) => panic!("no error was detected"),
Ok(Err(Error::RuntimeError(_))) => {}
Ok(Err(e)) => panic!("unexpected error during panic test {:?}", e),
Ok(Err(e)) => panic!("expected RuntimeError, got {:?}", e),
Err(_) => panic!("panic was detected"),
};
}
Ok(())
}
@@ -457,7 +541,9 @@ fn test_num_conversion() -> Result<()> {
assert!(lua.load("math.huge").eval::<i64>().is_err());
assert_eq!(lua.unpack::<f64>(lua.pack(f32::MAX)?)?, f32::MAX as f64);
assert!(lua.unpack::<f32>(lua.pack(f64::MAX)?).is_err());
assert_eq!(lua.unpack::<f64>(lua.pack(f32::MIN)?)?, f32::MIN as f64);
assert_eq!(lua.unpack::<f32>(lua.pack(f64::MAX)?)?, f32::INFINITY);
assert_eq!(lua.unpack::<f32>(lua.pack(f64::MIN)?)?, f32::NEG_INFINITY);
assert_eq!(lua.unpack::<i128>(lua.pack(1i128 << 64)?)?, 1i128 << 64);
-12
View File
@@ -1,15 +1,3 @@
#![cfg_attr(
all(feature = "luajit", target_os = "macos", target_arch = "x86_64"),
feature(link_args)
)]
#[cfg_attr(
all(feature = "luajit", target_os = "macos", target_arch = "x86_64"),
link_args = "-pagezero_size 10000 -image_base 100000000",
allow(unused_attributes)
)]
extern "system" {}
use std::panic::catch_unwind;
use mlua::{Error, Function, Lua, Result, Thread, ThreadStatus};
-12
View File
@@ -1,15 +1,3 @@
#![cfg_attr(
all(feature = "luajit", target_os = "macos", target_arch = "x86_64"),
feature(link_args)
)]
#[cfg_attr(
all(feature = "luajit", target_os = "macos", target_arch = "x86_64"),
link_args = "-pagezero_size 10000 -image_base 100000000",
allow(unused_attributes)
)]
extern "system" {}
use std::os::raw::c_void;
use mlua::{Function, LightUserData, Lua, Result};
+2 -14
View File
@@ -1,15 +1,3 @@
#![cfg_attr(
all(feature = "luajit", target_os = "macos", target_arch = "x86_64"),
feature(link_args)
)]
#[cfg_attr(
all(feature = "luajit", target_os = "macos", target_arch = "x86_64"),
link_args = "-pagezero_size 10000 -image_base 100000000",
allow(unused_attributes)
)]
extern "system" {}
use std::sync::Arc;
#[cfg(feature = "lua54")]
@@ -25,8 +13,8 @@ fn test_user_data() -> Result<()> {
struct UserData1(i64);
struct UserData2(Box<i64>);
impl UserData for UserData1 {};
impl UserData for UserData2 {};
impl UserData for UserData1 {}
impl UserData for UserData2 {}
let lua = Lua::new();
let userdata1 = lua.create_userdata(UserData1(1))?;
-12
View File
@@ -1,15 +1,3 @@
#![cfg_attr(
all(feature = "luajit", target_os = "macos", target_arch = "x86_64"),
feature(link_args)
)]
#[cfg_attr(
all(feature = "luajit", target_os = "macos", target_arch = "x86_64"),
link_args = "-pagezero_size 10000 -image_base 100000000",
allow(unused_attributes)
)]
extern "system" {}
use mlua::{Lua, Result, Value};
#[test]