Add `UserDataFields::add_field()` method to add static fields to UserData.

Plus `UserDataFields::add_meta_field()` for static meta fields.
Fix propagating fields to wrapped UserData types.
This commit is contained in:
Alex Orlenko 2023-06-07 12:18:24 +01:00
parent e7b712e29f
commit 5a135a331a
No known key found for this signature in database
GPG Key ID: 4C150C250863B96D
6 changed files with 218 additions and 138 deletions

View File

@ -1857,7 +1857,10 @@ impl Lua {
pub fn scope<'lua, 'scope, R>(
&'lua self,
f: impl FnOnce(&Scope<'lua, 'scope>) -> Result<R>,
) -> Result<R> {
) -> Result<R>
where
'lua: 'scope,
{
f(&Scope::new(self))
}
@ -2487,7 +2490,7 @@ impl Lua {
unsafe fn register_userdata_metatable<'lua, T: 'static>(
&'lua self,
registry: UserDataRegistrar<'lua, T>,
mut registry: UserDataRegistrar<'lua, T>,
) -> Result<Integer> {
let state = self.state();
let _sg = StackGuard::new(state);
@ -2510,7 +2513,7 @@ impl Lua {
let mut has_name = false;
for (k, f) in registry.meta_fields {
has_name = has_name || k == "__name";
self.push_value(f(self)?)?;
self.push_value(f(self, MultiValue::new())?.pop_front().unwrap())?;
rawset_field(state, -2, MetaMethod::validate(&k)?)?;
}
// Set `__name` if not provided
@ -2523,6 +2526,32 @@ impl Lua {
let mut extra_tables_count = 0;
let fields_nrec = registry.fields.len();
if fields_nrec > 0 {
// If __index is a table then update it inplace
let index_type = ffi::lua_getfield(state, metatable_index, cstr!("__index"));
match index_type {
ffi::LUA_TNIL | ffi::LUA_TTABLE => {
if index_type == ffi::LUA_TNIL {
// Create a new table
ffi::lua_pop(state, 1);
push_table(state, 0, fields_nrec as c_int, true)?;
}
for (k, f) in registry.fields {
self.push_value(f(self, MultiValue::new())?.pop_front().unwrap())?;
rawset_field(state, -2, &k)?;
}
rawset_field(state, metatable_index, "__index")?;
}
_ => {
// Propagate fields to the field getters
for (k, f) in registry.fields {
registry.field_getters.push((k, f))
}
}
}
}
let mut field_getters_index = None;
let field_getters_nrec = registry.field_getters.len();
if field_getters_nrec > 0 {
@ -2552,7 +2581,16 @@ impl Lua {
#[cfg(feature = "async")]
let methods_nrec = methods_nrec + registry.async_methods.len();
if methods_nrec > 0 {
push_table(state, 0, methods_nrec as c_int, true)?;
// If __index is a table then update it inplace
let index_type = ffi::lua_getfield(state, metatable_index, cstr!("__index"));
match index_type {
ffi::LUA_TTABLE => {} // Update the existing table
_ => {
// Create a new table
ffi::lua_pop(state, 1);
push_table(state, 0, methods_nrec as c_int, true)?;
}
}
for (k, m) in registry.methods {
self.push_value(Value::Function(self.create_callback(m)?))?;
rawset_field(state, -2, &k)?;
@ -2562,8 +2600,18 @@ impl Lua {
self.push_value(Value::Function(self.create_async_callback(m)?))?;
rawset_field(state, -2, &k)?;
}
methods_index = Some(ffi::lua_absindex(state, -1));
extra_tables_count += 1;
match index_type {
ffi::LUA_TTABLE => {
ffi::lua_pop(state, 1); // All done
}
ffi::LUA_TNIL => {
rawset_field(state, metatable_index, "__index")?; // Set the new table as __index
}
_ => {
methods_index = Some(ffi::lua_absindex(state, -1));
extra_tables_count += 1;
}
}
}
init_userdata_metatable::<UserDataCell<T>>(

View File

@ -14,6 +14,7 @@ use crate::types::{Callback, CallbackUpvalue, LuaRef, MaybeSend};
use crate::userdata::{
AnyUserData, MetaMethod, UserData, UserDataCell, UserDataFields, UserDataMethods,
};
use crate::userdata_impl::UserDataRegistrar;
use crate::util::{
assert_stack, check_stack, get_userdata, init_userdata_metatable, push_table, rawset_field,
take_userdata, StackGuard,
@ -32,7 +33,10 @@ use std::future::Future;
/// See [`Lua::scope`] for more details.
///
/// [`Lua::scope`]: crate::Lua.html::scope
pub struct Scope<'lua, 'scope> {
pub struct Scope<'lua, 'scope>
where
'lua: 'scope,
{
lua: &'lua Lua,
destructors: RefCell<Vec<(LuaRef<'lua>, DestructorCallback<'lua>)>>,
_scope_invariant: PhantomData<Cell<&'scope ()>>,
@ -393,7 +397,7 @@ impl<'lua, 'scope> Scope<'lua, 'scope> {
rawset_field(state, -2, MetaMethod::validate(&k)?)?;
}
for (k, f) in ud_fields.meta_fields {
lua.push_value(f(mem::transmute(lua))?)?;
lua.push_value(f(lua, MultiValue::new())?.pop_front().unwrap())?;
rawset_field(state, -2, MetaMethod::validate(&k)?)?;
}
let metatable_index = ffi::lua_absindex(state, -1);
@ -734,15 +738,16 @@ impl<'lua, T: UserData> UserDataMethods<'lua, T> for NonStaticUserDataMethods<'l
}
struct NonStaticUserDataFields<'lua, T: UserData> {
fields: Vec<(String, Callback<'lua, 'static>)>,
field_getters: Vec<(String, NonStaticMethod<'lua, T>)>,
field_setters: Vec<(String, NonStaticMethod<'lua, T>)>,
#[allow(clippy::type_complexity)]
meta_fields: Vec<(String, Box<dyn Fn(&'lua Lua) -> Result<Value<'lua>>>)>,
meta_fields: Vec<(String, Callback<'lua, 'static>)>,
}
impl<'lua, T: UserData> Default for NonStaticUserDataFields<'lua, T> {
fn default() -> NonStaticUserDataFields<'lua, T> {
NonStaticUserDataFields {
fields: Vec::new(),
field_getters: Vec::new(),
field_setters: Vec::new(),
meta_fields: Vec::new(),
@ -751,6 +756,17 @@ impl<'lua, T: UserData> Default for NonStaticUserDataFields<'lua, T> {
}
impl<'lua, T: UserData> UserDataFields<'lua, T> for NonStaticUserDataFields<'lua, T> {
fn add_field<V>(&mut self, name: impl AsRef<str>, value: V)
where
V: IntoLua<'lua> + Clone + 'static,
{
let name = name.as_ref().to_string();
self.fields.push((
name,
Box::new(move |lua, _| value.clone().into_lua_multi(lua)),
));
}
fn add_field_method_get<M, R>(&mut self, name: impl AsRef<str>, method: M)
where
M: Fn(&'lua Lua, &T) -> Result<R> + MaybeSend + 'static,
@ -796,30 +812,30 @@ impl<'lua, T: UserData> UserDataFields<'lua, T> for NonStaticUserDataFields<'lua
self.field_setters.push((name.as_ref().into(), func));
}
fn add_meta_field<V>(&mut self, name: impl AsRef<str>, value: V)
where
V: IntoLua<'lua> + Clone + 'static,
{
let name = name.as_ref().to_string();
let name2 = name.clone();
self.meta_fields.push((
name,
Box::new(move |lua, _| {
UserDataRegistrar::<()>::check_meta_field(lua, &name2, value.clone())
}),
));
}
fn add_meta_field_with<F, R>(&mut self, name: impl AsRef<str>, f: F)
where
F: Fn(&'lua Lua) -> Result<R> + MaybeSend + 'static,
R: IntoLua<'lua>,
{
let name = name.as_ref().to_string();
let name2 = name.clone();
self.meta_fields.push((
name.clone(),
Box::new(move |lua| {
let value = f(lua)?.into_lua(lua)?;
if name == MetaMethod::Index || name == MetaMethod::NewIndex {
match value {
Value::Nil | Value::Table(_) | Value::Function(_) => {}
_ => {
return Err(Error::MetaMethodTypeError {
method: name.clone(),
type_name: value.type_name(),
message: Some("expected nil, table or function".to_string()),
})
}
}
}
Ok(value)
}),
name,
Box::new(move |lua, _| UserDataRegistrar::<()>::check_meta_field(lua, &name2, f(lua)?)),
));
}
}

View File

@ -21,12 +21,10 @@ use crate::function::Function;
use crate::lua::Lua;
use crate::string::String;
use crate::table::{Table, TablePairs};
use crate::types::{Callback, LuaRef, MaybeSend};
use crate::types::{LuaRef, MaybeSend};
use crate::util::{check_stack, get_userdata, take_userdata, StackGuard};
use crate::value::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti, Value};
#[cfg(feature = "async")]
use crate::types::AsyncCallback;
use crate::UserDataRegistrar;
#[cfg(feature = "lua54")]
pub(crate) const USER_VALUE_MAXSLOT: usize = 8;
@ -412,29 +410,26 @@ pub trait UserDataMethods<'lua, T> {
//
#[doc(hidden)]
fn add_callback(&mut self, _name: StdString, _callback: Callback<'lua, 'static>) {}
#[doc(hidden)]
#[cfg(feature = "async")]
fn add_async_callback(&mut self, _name: StdString, _callback: AsyncCallback<'lua, 'static>) {}
#[doc(hidden)]
fn add_meta_callback(&mut self, _name: StdString, _callback: Callback<'lua, 'static>) {}
#[doc(hidden)]
#[cfg(feature = "async")]
fn add_async_meta_callback(
&mut self,
_name: StdString,
_callback: AsyncCallback<'lua, 'static>,
) {
}
fn append_methods_from<S>(&mut self, _other: UserDataRegistrar<'lua, S>) {}
}
/// Field registry for [`UserData`] implementors.
///
/// [`UserData`]: crate::UserData
pub trait UserDataFields<'lua, T> {
/// Add a static field to the `UserData`.
///
/// Static fields are implemented by updating the `__index` metamethod and returning the
/// accessed field. This allows them to be used with the expected `userdata.field` syntax.
///
/// Static fields are usually shared between all instances of the `UserData` of the same type.
///
/// If `add_meta_method` is used to set the `__index` metamethod, it will
/// be used as a fall-back if no regular field or method are found.
fn add_field<V>(&mut self, name: impl AsRef<str>, value: V)
where
V: IntoLua<'lua> + Clone + 'static;
/// Add a regular field getter as a method which accepts a `&T` as the parameter.
///
/// Regular field getters are implemented by overriding the `__index` metamethod and returning the
@ -483,9 +478,21 @@ pub trait UserDataFields<'lua, T> {
F: FnMut(&'lua Lua, AnyUserData<'lua>, A) -> Result<()> + MaybeSend + 'static,
A: FromLua<'lua>;
/// Add a metamethod value computed from `f`.
/// Add a metatable field.
///
/// This will initialize the metamethod value from `f` on `UserData` creation.
/// This will initialize the metatable field with `value` on `UserData` creation.
///
/// # Note
///
/// `mlua` will trigger an error on an attempt to define a protected metamethod,
/// like `__gc` or `__metatable`.
fn add_meta_field<V>(&mut self, name: impl AsRef<str>, value: V)
where
V: IntoLua<'lua> + Clone + 'static;
/// Add a metatable field computed from `f`.
///
/// This will initialize the metatable field from `f` on `UserData` creation.
///
/// # Note
///
@ -501,10 +508,7 @@ pub trait UserDataFields<'lua, T> {
//
#[doc(hidden)]
fn add_field_getter(&mut self, _name: StdString, _callback: Callback<'lua, 'static>) {}
#[doc(hidden)]
fn add_field_setter(&mut self, _name: StdString, _callback: Callback<'lua, 'static>) {}
fn append_fields_from<S>(&mut self, _other: UserDataRegistrar<'lua, S>) {}
}
/// Trait for custom userdata types.

View File

@ -11,7 +11,7 @@ use crate::userdata::{
AnyUserData, MetaMethod, UserData, UserDataCell, UserDataFields, UserDataMethods,
};
use crate::util::{check_stack, get_userdata, short_type_name, StackGuard};
use crate::value::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti, Value};
use crate::value::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti, MultiValue, Value};
#[cfg(not(feature = "send"))]
use std::rc::Rc;
@ -25,13 +25,10 @@ use {
pub struct UserDataRegistrar<'lua, T: 'static> {
// Fields
pub(crate) fields: Vec<(String, Callback<'lua, 'static>)>,
pub(crate) field_getters: Vec<(String, Callback<'lua, 'static>)>,
pub(crate) field_setters: Vec<(String, Callback<'lua, 'static>)>,
#[allow(clippy::type_complexity)]
pub(crate) meta_fields: Vec<(
String,
Box<dyn Fn(&'lua Lua) -> Result<Value<'lua>> + 'static>,
)>,
pub(crate) meta_fields: Vec<(String, Callback<'lua, 'static>)>,
// Methods
pub(crate) methods: Vec<(String, Callback<'lua, 'static>)>,
@ -47,6 +44,7 @@ pub struct UserDataRegistrar<'lua, T: 'static> {
impl<'lua, T: 'static> UserDataRegistrar<'lua, T> {
pub(crate) const fn new() -> Self {
UserDataRegistrar {
fields: Vec::new(),
field_getters: Vec::new(),
field_setters: Vec::new(),
meta_fields: Vec::new(),
@ -360,6 +358,30 @@ impl<'lua, T: 'static> UserDataRegistrar<'lua, T> {
)
})
}
pub(crate) fn check_meta_field<V>(
lua: &'lua Lua,
name: &str,
value: V,
) -> Result<MultiValue<'lua>>
where
V: IntoLua<'lua>,
{
let value = value.into_lua(lua)?;
if name == MetaMethod::Index || name == MetaMethod::NewIndex {
match value {
Value::Nil | Value::Table(_) | Value::Function(_) => {}
_ => {
return Err(Error::MetaMethodTypeError {
method: name.to_string(),
type_name: value.type_name(),
message: Some("expected nil, table or function".to_string()),
})
}
}
}
value.into_lua_multi(lua)
}
}
// Returns function name for the type `T`, without the module path
@ -368,6 +390,17 @@ fn get_function_name<T>(name: &str) -> StdString {
}
impl<'lua, T: 'static> UserDataFields<'lua, T> for UserDataRegistrar<'lua, T> {
fn add_field<V>(&mut self, name: impl AsRef<str>, value: V)
where
V: IntoLua<'lua> + Clone + 'static,
{
let name = name.as_ref().to_string();
self.fields.push((
name,
Box::new(move |lua, _| value.clone().into_lua_multi(lua)),
));
}
fn add_field_method_get<M, R>(&mut self, name: impl AsRef<str>, method: M)
where
M: Fn(&'lua Lua, &T) -> Result<R> + MaybeSend + 'static,
@ -408,41 +441,38 @@ impl<'lua, T: 'static> UserDataFields<'lua, T> for UserDataRegistrar<'lua, T> {
self.field_setters.push((name.into(), func));
}
fn add_meta_field<V>(&mut self, name: impl AsRef<str>, value: V)
where
V: IntoLua<'lua> + Clone + 'static,
{
let name = name.as_ref().to_string();
let name2 = name.clone();
self.meta_fields.push((
name,
Box::new(move |lua, _| Self::check_meta_field(lua, &name2, value.clone())),
));
}
fn add_meta_field_with<F, R>(&mut self, name: impl AsRef<str>, f: F)
where
F: Fn(&'lua Lua) -> Result<R> + MaybeSend + 'static,
R: IntoLua<'lua>,
{
let name = name.as_ref().to_string();
let name2 = name.clone();
self.meta_fields.push((
name.clone(),
Box::new(move |lua| {
let value = f(lua)?.into_lua(lua)?;
if name == MetaMethod::Index || name == MetaMethod::NewIndex {
match value {
Value::Nil | Value::Table(_) | Value::Function(_) => {}
_ => {
return Err(Error::MetaMethodTypeError {
method: name.clone(),
type_name: value.type_name(),
message: Some("expected nil, table or function".to_string()),
})
}
}
}
Ok(value)
}),
name,
Box::new(move |lua, _| Self::check_meta_field(lua, &name2, f(lua)?)),
));
}
// Below are internal methods
fn add_field_getter(&mut self, name: String, callback: Callback<'lua, 'static>) {
self.field_getters.push((name, callback));
}
fn add_field_setter(&mut self, name: String, callback: Callback<'lua, 'static>) {
self.field_setters.push((name, callback));
fn append_fields_from<S>(&mut self, other: UserDataRegistrar<'lua, S>) {
self.fields.extend(other.fields);
self.field_getters.extend(other.field_getters);
self.field_setters.extend(other.field_setters);
self.meta_fields.extend(other.meta_fields);
}
}
@ -591,22 +621,13 @@ impl<'lua, T: 'static> UserDataMethods<'lua, T> for UserDataRegistrar<'lua, T> {
// Below are internal methods used in generated code
fn add_callback(&mut self, name: String, callback: Callback<'lua, 'static>) {
self.methods.push((name, callback));
}
#[cfg(feature = "async")]
fn add_async_callback(&mut self, name: String, callback: AsyncCallback<'lua, 'static>) {
self.async_methods.push((name, callback));
}
fn add_meta_callback(&mut self, name: String, callback: Callback<'lua, 'static>) {
self.meta_methods.push((name, callback));
}
#[cfg(feature = "async")]
fn add_async_meta_callback(&mut self, meta: String, callback: AsyncCallback<'lua, 'static>) {
self.async_meta_methods.push((meta, callback))
fn append_methods_from<S>(&mut self, other: UserDataRegistrar<'lua, S>) {
self.methods.extend(other.methods);
#[cfg(feature = "async")]
self.async_methods.extend(other.async_methods);
self.meta_methods.extend(other.meta_methods);
#[cfg(feature = "async")]
self.async_meta_methods.extend(other.async_meta_methods);
}
}
@ -626,31 +647,13 @@ macro_rules! lua_userdata_impl {
fn add_fields<'lua, F: UserDataFields<'lua, Self>>(fields: &mut F) {
let mut orig_fields = UserDataRegistrar::new();
T::add_fields(&mut orig_fields);
for (name, callback) in orig_fields.field_getters {
fields.add_field_getter(name, callback);
}
for (name, callback) in orig_fields.field_setters {
fields.add_field_setter(name, callback);
}
fields.append_fields_from(orig_fields);
}
fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) {
let mut orig_methods = UserDataRegistrar::new();
T::add_methods(&mut orig_methods);
for (name, callback) in orig_methods.methods {
methods.add_callback(name, callback);
}
#[cfg(feature = "async")]
for (name, callback) in orig_methods.async_methods {
methods.add_async_callback(name, callback);
}
for (meta, callback) in orig_methods.meta_methods {
methods.add_meta_callback(meta, callback);
}
#[cfg(feature = "async")]
for (meta, callback) in orig_methods.async_meta_methods {
methods.add_async_meta_callback(meta, callback);
}
methods.append_methods_from(orig_methods);
}
}
};

View File

@ -402,9 +402,12 @@ unsafe extern "C" fn lua_error_impl(state: *mut ffi::lua_State) -> c_int {
}
unsafe extern "C" fn lua_isfunction_impl(state: *mut ffi::lua_State) -> c_int {
let t = ffi::lua_type(state, -1);
ffi::lua_pop(state, 1);
ffi::lua_pushboolean(state, (t == ffi::LUA_TFUNCTION) as c_int);
ffi::lua_pushboolean(state, ffi::lua_isfunction(state, -1));
1
}
unsafe extern "C" fn lua_istable_impl(state: *mut ffi::lua_State) -> c_int {
ffi::lua_pushboolean(state, ffi::lua_istable(state, -1));
1
}
@ -418,14 +421,19 @@ unsafe fn init_userdata_metatable_index(state: *mut ffi::lua_State) -> Result<()
// Create and cache `__index` generator
let code = cstr!(
r#"
local error, isfunction = ...
local error, isfunction, istable = ...
return function (__index, field_getters, methods)
-- Fastpath to return methods table for index access
if __index == nil and field_getters == nil then
return methods
-- Common case: has field getters and index is a table
if field_getters ~= nil and methods == nil and istable(__index) then
return function (self, key)
local field_getter = field_getters[key]
if field_getter ~= nil then
return field_getter(self)
end
return __index[key]
end
end
-- Alternatively return a function for index access
return function (self, key)
if field_getters ~= nil then
local field_getter = field_getters[key]
@ -460,7 +468,8 @@ unsafe fn init_userdata_metatable_index(state: *mut ffi::lua_State) -> Result<()
}
ffi::lua_pushcfunction(state, lua_error_impl);
ffi::lua_pushcfunction(state, lua_isfunction_impl);
ffi::lua_call(state, 2, 1);
ffi::lua_pushcfunction(state, lua_istable_impl);
ffi::lua_call(state, 3, 1);
#[cfg(feature = "luau-jit")]
if ffi::luau_codegen_supported() != 0 {

View File

@ -1,3 +1,4 @@
use std::collections::HashMap;
use std::string::String as StdString;
use std::sync::Arc;
#[cfg(not(feature = "parking_lot"))]
@ -486,6 +487,7 @@ fn test_fields() -> Result<()> {
impl UserData for MyUserData {
fn add_fields<'lua, F: UserDataFields<'lua, Self>>(fields: &mut F) {
fields.add_field("static", "constant");
fields.add_field_method_get("val", |_, data| Ok(data.0));
fields.add_field_method_set("val", |_, data, val| {
data.0 = val;
@ -497,11 +499,7 @@ fn test_fields() -> Result<()> {
fields
.add_field_function_set("uval", |_, ud, s| ud.set_user_value::<Option<String>>(s));
fields.add_meta_field_with(MetaMethod::Index, |lua| {
let index = lua.create_table()?;
index.set("f", 321)?;
Ok(index)
});
fields.add_meta_field(MetaMethod::Index, HashMap::from([("f", 321)]));
fields.add_meta_field_with(MetaMethod::NewIndex, |lua| {
lua.create_function(|lua, (_, field, val): (AnyUserData, String, Value)| {
lua.globals().set(field, val)?;
@ -516,6 +514,7 @@ fn test_fields() -> Result<()> {
globals.set("ud", MyUserData(7))?;
lua.load(
r#"
assert(ud.static == "constant")
assert(ud.val == 7)
ud.val = 10
assert(ud.val == 10)
@ -614,6 +613,7 @@ fn test_userdata_wrapped() -> Result<()> {
impl UserData for MyUserData {
fn add_fields<'lua, F: UserDataFields<'lua, Self>>(fields: &mut F) {
fields.add_field("static", "constant");
fields.add_field_method_get("data", |_, this| Ok(this.0));
fields.add_field_method_set("data", |_, this, val| {
this.0 = val;
@ -631,6 +631,7 @@ fn test_userdata_wrapped() -> Result<()> {
globals.set("rc_refcell_ud", ud1.clone())?;
lua.load(
r#"
assert(rc_refcell_ud.static == "constant")
rc_refcell_ud.data = rc_refcell_ud.data + 1
assert(rc_refcell_ud.data == 2)
"#,
@ -646,6 +647,7 @@ fn test_userdata_wrapped() -> Result<()> {
globals.set("arc_mutex_ud", ud2.clone())?;
lua.load(
r#"
assert(arc_mutex_ud.static == "constant")
arc_mutex_ud.data = arc_mutex_ud.data + 1
assert(arc_mutex_ud.data == 3)
"#,
@ -660,6 +662,7 @@ fn test_userdata_wrapped() -> Result<()> {
globals.set("arc_rwlock_ud", ud3.clone())?;
lua.load(
r#"
assert(arc_rwlock_ud.static == "constant")
arc_rwlock_ud.data = arc_rwlock_ud.data + 1
assert(arc_rwlock_ud.data == 4)
"#,
@ -686,7 +689,7 @@ fn test_userdata_proxy() -> Result<()> {
impl UserData for MyUserData {
fn add_fields<'lua, F: UserDataFields<'lua, Self>>(fields: &mut F) {
fields.add_field_function_get("static_field", |_, _| Ok(123));
fields.add_field("static_field", 123);
fields.add_field_method_get("n", |_, this| Ok(this.0));
}
@ -780,10 +783,7 @@ fn test_userdata_ext() -> Result<()> {
assert_eq!(ud.get::<_, u32>("n")?, 123);
ud.set("n", 321)?;
assert_eq!(ud.get::<_, u32>("n")?, 321);
match ud.get::<_, u32>("non-existent") {
Err(Error::RuntimeError(_)) => {}
r => panic!("expected RuntimeError, got {r:?}"),
}
assert_eq!(ud.get::<_, Option<u32>>("non-existent")?, None);
match ud.set::<_, u32>("non-existent", 123) {
Err(Error::RuntimeError(_)) => {}
r => panic!("expected RuntimeError, got {r:?}"),