Skip to main content

luars/lua_value/
lua_convert.rs

1//! `FromLua` / `IntoLua` — bidirectional conversion between Rust types and `LuaValue`.
2//!
3//! These traits allow Lua function arguments and return values to be expressed
4//! with native Rust types instead of manually calling `get_arg` / `push_value`.
5//!
6//! # Built-in impls
7//! - `()`, `bool`, `i8`..`i64`, `u8`..`u64`, `f32`, `f64`
8//! - `String`, `&str` (via intermediate `String`)
9//! - `Option<T>` where `T: FromLua` / `T: IntoLua`
10//! - `LuaValue` (identity — zero-cost passthrough)
11//!
12//! # Usage in derive macros
13//! The `#[lua_methods]` macro generates calls to `FromLua::from_lua` for each
14//! parameter and `IntoLua::into_lua` for the return value, keeping the codegen
15//! type-agnostic and user-extensible.
16//!
17//! # User extensibility
18//! Users can implement `FromLua` / `IntoLua` for their own types:
19//! ```ignore
20//! impl FromLua for MyVec3 {
21//!     fn from_lua(value: LuaValue, state: &LuaState) -> Result<Self, String> {
22//!         // extract from userdata or table
23//!     }
24//! }
25//! ```
26
27use crate::lua_value::LuaValue;
28use crate::lua_vm::LuaState;
29
30/// Convert a `LuaValue` into a Rust type.
31///
32/// Implementors define how a Lua value is converted to `Self`.
33/// Return `Err(message)` for type mismatches.
34pub trait FromLua: Sized {
35    /// Convert a `LuaValue` to `Self`.
36    ///
37    /// `state` is provided for operations that need GC access (e.g. string interning).
38    fn from_lua(value: LuaValue, state: &LuaState) -> Result<Self, String>;
39}
40
41/// Convert a Rust type into a `LuaValue` and push it.
42///
43/// Implementors define how `self` becomes one or more Lua values on the stack.
44/// Returns the number of values pushed (typically 1, or 0 for `()`).
45pub trait IntoLua {
46    /// Push this value onto the Lua stack.
47    ///
48    /// Returns the number of Lua values pushed.
49    fn into_lua(self, state: &mut LuaState) -> Result<usize, String>;
50}
51
52// ==================== Identity: LuaValue ====================
53
54impl FromLua for LuaValue {
55    #[inline]
56    fn from_lua(value: LuaValue, _state: &LuaState) -> Result<Self, String> {
57        Ok(value)
58    }
59}
60
61impl IntoLua for LuaValue {
62    #[inline]
63    fn into_lua(self, state: &mut LuaState) -> Result<usize, String> {
64        state.push_value(self).map_err(|e| format!("{:?}", e))?;
65        Ok(1)
66    }
67}
68
69// ==================== Unit ====================
70
71impl FromLua for () {
72    #[inline]
73    fn from_lua(_value: LuaValue, _state: &LuaState) -> Result<Self, String> {
74        Ok(())
75    }
76}
77
78impl IntoLua for () {
79    #[inline]
80    fn into_lua(self, _state: &mut LuaState) -> Result<usize, String> {
81        Ok(0)
82    }
83}
84
85// ==================== Boolean ====================
86
87impl FromLua for bool {
88    #[inline]
89    fn from_lua(value: LuaValue, _state: &LuaState) -> Result<Self, String> {
90        // Follow Lua truthiness: nil and false → false, everything else → true
91        Ok(value.as_boolean().unwrap_or(!value.is_nil()))
92    }
93}
94
95impl IntoLua for bool {
96    #[inline]
97    fn into_lua(self, state: &mut LuaState) -> Result<usize, String> {
98        state
99            .push_value(LuaValue::boolean(self))
100            .map_err(|e| format!("{:?}", e))?;
101        Ok(1)
102    }
103}
104
105// ==================== Integer types ====================
106
107macro_rules! impl_from_lua_int {
108    ($($ty:ty),*) => {
109        $(
110            impl FromLua for $ty {
111                #[inline]
112                fn from_lua(value: LuaValue, _state: &LuaState) -> Result<Self, String> {
113                    if let Some(i) = value.as_integer() {
114                        Ok(i as $ty)
115                    } else if let Some(f) = value.as_float() {
116                        Ok(f as $ty)
117                    } else {
118                        Err(format!("expected integer, got {}", value.type_name()))
119                    }
120                }
121            }
122
123            impl IntoLua for $ty {
124                #[inline]
125                fn into_lua(self, state: &mut LuaState) -> Result<usize, String> {
126                    state
127                        .push_value(LuaValue::integer(self as i64))
128                        .map_err(|e| format!("{:?}", e))?;
129                    Ok(1)
130                }
131            }
132        )*
133    };
134}
135
136impl_from_lua_int!(i8, i16, i32, i64, isize, u8, u16, u32, u64, usize);
137
138// ==================== Float types ====================
139
140macro_rules! impl_from_lua_float {
141    ($($ty:ty),*) => {
142        $(
143            impl FromLua for $ty {
144                #[inline]
145                fn from_lua(value: LuaValue, _state: &LuaState) -> Result<Self, String> {
146                    if let Some(n) = value.as_number() {
147                        Ok(n as $ty)
148                    } else if let Some(i) = value.as_integer() {
149                        Ok(i as $ty)
150                    } else {
151                        Err(format!("expected number, got {}", value.type_name()))
152                    }
153                }
154            }
155
156            impl IntoLua for $ty {
157                #[inline]
158                fn into_lua(self, state: &mut LuaState) -> Result<usize, String> {
159                    state
160                        .push_value(LuaValue::float(self as f64))
161                        .map_err(|e| format!("{:?}", e))?;
162                    Ok(1)
163                }
164            }
165        )*
166    };
167}
168
169impl_from_lua_float!(f32, f64);
170
171// ==================== String ====================
172
173impl FromLua for String {
174    #[inline]
175    fn from_lua(value: LuaValue, _state: &LuaState) -> Result<Self, String> {
176        if let Some(s) = value.as_str() {
177            Ok(s.to_owned())
178        } else if let Some(i) = value.as_integer() {
179            // Lua coerces numbers to strings
180            Ok(format!("{}", i))
181        } else if let Some(f) = value.as_float() {
182            Ok(format!("{}", f))
183        } else {
184            Err(format!("expected string, got {}", value.type_name()))
185        }
186    }
187}
188
189impl IntoLua for String {
190    #[inline]
191    fn into_lua(self, state: &mut LuaState) -> Result<usize, String> {
192        let s = state.create_string(&self).map_err(|e| format!("{:?}", e))?;
193        state.push_value(s).map_err(|e| format!("{:?}", e))?;
194        Ok(1)
195    }
196}
197
198impl IntoLua for &str {
199    #[inline]
200    fn into_lua(self, state: &mut LuaState) -> Result<usize, String> {
201        let s = state.create_string(self).map_err(|e| format!("{:?}", e))?;
202        state.push_value(s).map_err(|e| format!("{:?}", e))?;
203        Ok(1)
204    }
205}
206
207// ==================== Option<T> ====================
208
209impl<T: FromLua> FromLua for Option<T> {
210    #[inline]
211    fn from_lua(value: LuaValue, state: &LuaState) -> Result<Self, String> {
212        if value.is_nil() {
213            Ok(None)
214        } else {
215            T::from_lua(value, state).map(Some)
216        }
217    }
218}
219
220impl<T: IntoLua> IntoLua for Option<T> {
221    #[inline]
222    fn into_lua(self, state: &mut LuaState) -> Result<usize, String> {
223        match self {
224            Some(v) => v.into_lua(state),
225            None => {
226                state
227                    .push_value(LuaValue::nil())
228                    .map_err(|e| format!("{:?}", e))?;
229                Ok(1)
230            }
231        }
232    }
233}
234
235// ==================== Result<T, E> ====================
236
237impl<T: IntoLua, E: std::fmt::Display> IntoLua for Result<T, E> {
238    #[inline]
239    fn into_lua(self, state: &mut LuaState) -> Result<usize, String> {
240        match self {
241            Ok(v) => v.into_lua(state),
242            Err(e) => Err(format!("{}", e)),
243        }
244    }
245}
246
247// ==================== Vec<T> (push as multiple returns) ====================
248
249impl<T: IntoLua> IntoLua for Vec<T> {
250    #[inline]
251    fn into_lua(self, state: &mut LuaState) -> Result<usize, String> {
252        let count = self.len();
253        for item in self {
254            item.into_lua(state)?;
255        }
256        Ok(count)
257    }
258}