
Description
While it's impossible to derive NativeClass
in the general case due to monomorphization, it should be possible to export a list of monomorphizations specified by the user. This is most useful when a user needs to wrap a Rust data structure in a NativeScript, and know the list of types necessary.
Suppose that we have a GridMap
type that efficiently stores a value for each tile in a 3D space, for example. Currently, we can wrap it in a NativeClass
like this:
#[derive(NativeClass)]
#[inherit(Reference)]
struct GridMapInt {
map: GridMap<i32>,
}
#[methods]
impl GridMapInt {
#[export]
fn get(&self, _owner: &Reference, x: i32, y: i32, z: i32) -> i32 {
self.map.get(x, y, z)
}
#[export]
fn set(&self, _owner: &Reference, x: i32, y: i32, z: i32, value: i32) {
self.map.set(x, y, z, value);
}
// - 100 extra wrapper methods -
}
We can, then, create and use GridMap
s containing i32
s in GDScript. However, suppose that we now also want to be able to create GridMap
s that contain strings instead. Because NativeClass
and methods
don't support generics yet, we have no option but to copy and paste GridMapInt
, along with its 100 extra wrapper methods, which is obviously terrible, or use code generation, which is rather inconvenient. Both options will also limit how one can interact with those wrapper types from Rust, since they are not generic.
Instead, with a syntax for explicitly exporting certain monomorphizations, we would be able to write something like:
#[derive(NativeClass)]
#[inherit(Reference)]
struct GridMap<T> {
map: GridMap<T>,
}
#[gdnative::monomorphize]
type GridMapInt = GridMap<i32>;
#[gdnative::monomorphize]
type GridMapStr = GridMap<String>;
#[methods]
impl<T> GridMap<T> {
#[export]
fn get(&self, _owner: &Reference, x: i32, y: i32, z: i32) -> T {
self.map.get(x, y, z)
}
#[export]
fn set(&self, _owner: &Reference, x: i32, y: i32, z: i32, value: T) {
self.map.set(x, y, z, value);
}
// - 100 extra methods -
}
...which could expand to:
struct GridMap<T> {
map: GridMap<T>,
}
// `GenericNativeClass` will be a supertrait of `NativeClass` that does not have `class_name`.
impl<T> GenericNativeClass for GridMap<T> {
// - snip -
}
type GridMapInt = GridMap<i32>;
impl NativeClass for GridMapInt {
fn class_name() -> &'static str {
"GridMapInt"
}
}
type GridMapStr = GridMap<String>;
impl NativeClass for GridMapStr {
fn class_name() -> &'static str {
"GridMapStr"
}
}
impl<T> GridMap<T> {
fn get(&self, _owner: &Reference, x: i32, y: i32, z: i32) -> T {
self.map.get(x, y, z)
}
fn set(&self, _owner: &Reference, x: i32, y: i32, z: i32, value: T) {
self.map.set(x, y, z, value);
}
// - 100 extra methods -
}
impl<T> NativeClassMethods for GridMap<T>
where
// bounds detected from method signatures
T: FromVariant + OwnedToVariant,
{
fn register(builder: &ClassBuilder<Self>) {
// - snip -
}
}
Alternatives
Attributes on the original type instead of type aliases
Instead, we could also put the list of generic arguments above the type for which NativeScript
is derived:
#[derive(NativeClass)]
#[inherit(Reference)]
#[monomorphize(name = "GridMapInt", args = "i32")]
#[monomorphize(name = "GridMapStr", args = "String")]
struct GridMap<T> {
map: GridMap<T>,
}
There shouldn't be any implication on implementation difficulty or auto-registration, but I find this syntax much less convenient compared to the one with type aliases, due to how attribute syntax works. It also won't work as well with macros:
// This will work:
macro_rules! decl_aliases {
($($name:ident : $t:ty),*) => {
$(
#[gdnative::monomorphize]
type $name = MyStruct<With, An, Unusual, Number, Of, Arguments, $t>;
)*
}
}
decl_aliases!(
MyStructFoo: Foo,
MyStructBar: Bar
)
// ...while this won't, because attributes cannot be produced from ordinary macros in isolation:
macro_rules! decl_attrs {
($($name:ident : $t:ty),*) => {
// ..and even if they did, look at this monstrosity!
$(
#[monomorphize(
name = stringify!($name),
args = concat!(
"With, An, Unusual, Number, Of, Arguments, ",
stringify!($t),
),
)]
)*
}
}
Opinions are welcome!