forked from mlua-rs/rlua
-
Notifications
You must be signed in to change notification settings - Fork 0
/
guided_tour.rs
234 lines (185 loc) · 8.72 KB
/
guided_tour.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
use std::f32;
use std::iter::FromIterator;
use rlua::{Function, Lua, MetaMethod, Result, UserData, UserDataMethods, Variadic};
fn main() -> Result<()> {
// You can create a new Lua state with `Lua::new()`. This loads the default Lua std library
// *without* the debug library. You can get more control over this with the other
// `Lua::xxx_new_xxx` functions.
let lua = Lua::new();
// In order to interact with Lua values at all, you must do so inside a callback given to the
// `Lua::context` method. This provides some extra safety and allows the rlua API to avoid some
// extra runtime checks.
lua.context(|lua_ctx| {
// You can get and set global variables. Notice that the globals table here is a permanent
// reference to _G, and it is mutated behind the scenes as Lua code is loaded. This API is
// based heavily around sharing and internal mutation (just like Lua itself).
let globals = lua_ctx.globals();
globals.set("string_var", "hello")?;
globals.set("int_var", 42)?;
Ok(())
})?;
lua.context(|lua_ctx| {
// The Lua state lives inside the top-level `Lua` value, and all state changes persist
// between `Lua::context` calls. This is another table reference in another context call,
// but it refers to the same table _G.
let globals = lua_ctx.globals();
assert_eq!(globals.get::<_, String>("string_var")?, "hello");
assert_eq!(globals.get::<_, i64>("int_var")?, 42);
Ok(())
})?;
lua.context(|lua_ctx| {
let globals = lua_ctx.globals();
// You can load and evaluate Lua code. The returned type of `Context::load` is a builder
// that allows you to change settings before running Lua code. Here, we are using it to set
// the name of the laoded chunk to "example code", which will be used when Lua error
// messages are printed.
lua_ctx
.load(
r#"
global = 'foo'..'bar'
"#,
)
.set_name("example code")?
.exec()?;
assert_eq!(globals.get::<_, String>("global")?, "foobar");
assert_eq!(lua_ctx.load("1 + 1").eval::<i32>()?, 2);
assert_eq!(lua_ctx.load("false == false").eval::<bool>()?, true);
assert_eq!(lua_ctx.load("return 1 + 2").eval::<i32>()?, 3);
// You can create and manage Lua tables
let array_table = lua_ctx.create_table()?;
array_table.set(1, "one")?;
array_table.set(2, "two")?;
array_table.set(3, "three")?;
assert_eq!(array_table.len()?, 3);
let map_table = lua_ctx.create_table()?;
map_table.set("one", 1)?;
map_table.set("two", 2)?;
map_table.set("three", 3)?;
let v: i64 = map_table.get("two")?;
assert_eq!(v, 2);
// You can pass values like `Table` back into Lua
globals.set("array_table", array_table)?;
globals.set("map_table", map_table)?;
lua_ctx
.load(
r#"
for k, v in pairs(array_table) do
print(k, v)
end
for k, v in pairs(map_table) do
print(k, v)
end
"#,
)
.exec()?;
// You can load Lua functions
let print: Function = globals.get("print")?;
print.call::<_, ()>("hello from rust")?;
// This API generally handles variadics using tuples. This is one way to call a function with
// multiple parameters:
print.call::<_, ()>(("hello", "again", "from", "rust"))?;
// But, you can also pass variadic arguments with the `Variadic` type.
print.call::<_, ()>(Variadic::from_iter(
["hello", "yet", "again", "from", "rust"].iter().cloned(),
))?;
// You can bind rust functions to Lua as well. Callbacks receive a `Context` as their first
// parameter, and the arguments given to the function as the second parameter. The type of
// the arguments can be anything that is convertible from the parameters given by Lua, in
// this case, the function expects two string sequences.
let check_equal =
lua_ctx.create_function(|_, (list1, list2): (Vec<String>, Vec<String>)| {
// This function just checks whether two string lists are equal, and in an inefficient way.
// Lua callbacks return `rlua::Result`, an Ok value is a normal return, and an Err return
// turns into a Lua 'error'. Again, any type that is convertible to Lua may be returned.
Ok(list1 == list2)
})?;
globals.set("check_equal", check_equal)?;
// You can also accept runtime variadic arguments to rust callbacks.
let join = lua_ctx.create_function(|_, strings: Variadic<String>| {
// (This is quadratic!, it's just an example!)
Ok(strings.iter().fold("".to_owned(), |a, b| a + b))
})?;
globals.set("join", join)?;
assert_eq!(
lua_ctx
.load(r#"check_equal({"a", "b", "c"}, {"a", "b", "c"})"#)
.eval::<bool>()?,
true
);
assert_eq!(
lua_ctx
.load(r#"check_equal({"a", "b", "c"}, {"d", "e", "f"})"#)
.eval::<bool>()?,
false
);
assert_eq!(
lua_ctx.load(r#"join("a", "b", "c")"#).eval::<String>()?,
"abc"
);
// Callbacks receive a context value as their first parameter so that they can use it to
// create new Lua values, if necessary. Often times this is not necessary and the context
// parameter can be ignored.
let create_table = lua_ctx.create_function(|lua_ctx, ()| {
let t = lua_ctx.create_table()?;
t.set(1, 1)?;
t.set(2, 2)?;
Ok(t)
})?;
globals.set("create_table", create_table)?;
assert_eq!(lua_ctx.load(r#"create_table()[2]"#).eval::<i32>()?, 2);
// You can create userdata with methods and metamethods defined on them.
// Here's a worked example that shows many of the features of this API
// together
#[derive(Copy, Clone)]
struct Vec2(f32, f32);
impl UserData for Vec2 {
fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) {
methods.add_method("magnitude", |_, vec, ()| {
let mag_squared = vec.0 * vec.0 + vec.1 * vec.1;
Ok(mag_squared.sqrt())
});
methods.add_meta_function(MetaMethod::Add, |_, (vec1, vec2): (Vec2, Vec2)| {
Ok(Vec2(vec1.0 + vec2.0, vec1.1 + vec2.1))
});
}
}
let vec2_constructor = lua_ctx.create_function(|_, (x, y): (f32, f32)| Ok(Vec2(x, y)))?;
globals.set("vec2", vec2_constructor)?;
assert!(
(lua_ctx
.load("(vec2(1, 2) + vec2(2, 2)):magnitude()")
.eval::<f32>()?
- 5.0)
.abs()
< f32::EPSILON
);
// Normally, Rust types passed to `Lua` must be `Send`, because `Lua` itself is `Send`, and
// 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 both of these requirements. You can
// call `Context::scope` to create userdata and callbacks types that only live for as long
// as the call to scope, but do not have to be `Send` OR `'static`.
{
let mut rust_val = 0;
lua_ctx.scope(|scope| {
// We create a 'sketchy' Lua callback that holds a mutable reference to the variable
// `rust_val`. Outside of a `Context::scope` call, this would not be allowed
// because it could be unsafe.
lua_ctx.globals().set(
"sketchy",
scope.create_function_mut(|_, ()| {
rust_val = 42;
Ok(())
})?,
)?;
lua_ctx.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
// might be able to improperly access the freed `rust_val` which would be unsafe.
assert!(lua_ctx.load("sketchy()").exec().is_err());
Ok(())
})
}