ToVariant, FromVariant and Export
As seen in the previous section, the #[property]
attribute of the NativeClass
procedural macro is a powerful tool to automatically configure properties with Godot.
One constraint of the #[property]
attribute is that it requires that all attributed property types implement ToVariant
, FromVariant
and Export
in order to interface with Godot.
ToVariant/FromVariant traits
In Godot all types inherit from Variant.
As per the official Godot docs, Variant is "The most important data type in Godot." This is a wrapper type that can store any Godot Engine type
The ToVariant
and FromVariant
are conversion traits that allow Rust types to be converted between these types. All properties must implement both ToVariant
and FromVariant
while exported methods require FromVariant
to be implemented for optional parameters and ToVariant
to be implemented for return types.
For many datatypes, it is possible to use the derive macros such as in the following example:
#![allow(unused)] fn main() { // Note: This struct does not implement `Export` and cannot be used as a property, see the following section for more information. #[derive(ToVariant, FromVariant)] struct Foo { number: i32, float: f32, string: String } }
For more information about how you can customize the behavior of the dervive macros, please refer to the official documentation for the latest information.
Export Trait
The Godot editor retrieves property information from Object::get_property_list. To populate this data, godot-rust
requires that the Export
trait be implemented for each type Rust struct.
There are no derive macros that can be used for Export
but many of the primitive types have it implemented by default.
To implement Export
for the previous Rust data type, you can do so as in the following example:
#![allow(unused)] fn main() { // Note: By default `struct` will be converted to and from a Dictionary where property corresponds to a key-value pair. #[derive(ToVariant, FromVariant)] struct Foo { number: i32, float: f32, string: String } impl Export for Foo { // This type should normally be one of the types defined in [gdnative::export::hint](https://docs.rs/gdnative/latest/gdnative/export/hint/index.html). // Or it can be any custom type for differentiating the hint types. // In this case it is unused, so it is left as () type Hint = (); fn export_info(hint: Option<Self::Hint>) -> ExportInfo { // As `Foo` is a struct that will be converted to a Dictionary when converted to a variant, we can just add this as the VariantType. ExportInfo::new(VariantType::Dictionary) } } }
Case study: exporting Rust enums to Godot and back
A common challenge that many developers may encounter when using godot-rust is that while Rust enums are Algebraic Data Types, Godot enums are constants that correspond to integer types.
By default, Rust enums are converted to a Dictionary representation. Its keys correspond to the name of the enum variants, while the values correspond to a Dictionary with fields as key-value pairs.
For example:
#![allow(unused)] fn main() { #[derive(ToVariant, FromVariant)] enum MyEnum { A, B { inner: i32 }, C { inner: String } } }
Will convert to the following dictionary:
# MyEnum::A
"{ "A": {} }
# MyEnum::B { inner: 0 }
{ "B": { "inner": 0 } }
# MyEnum::C { inner: "value" }
{ "C": {"inner": "value" } }
As of writing (gdnative 0.9.3), this default case is not configurable. If you want different behavior, it is necessary to implement FromVariant
and Export
manually for this data-type.
Case 1: Rust Enum -> Godot Enum
Consider the following code:
#![allow(unused)] fn main() { enum MyIntEnum { A=0, B=1, C=2, } #[derive(NativeClass)] #[inherit(Node)] #[no_constructor] struct MyNode { #[property] int_enum: MyIntEnum } }
This code defines the enum MyIntEnum
, where each enum value refers to an integer value.
Without implementing the FromVariant
and Export
traits, attempting to export MyIntEnum
as a property of MyNode
will result in the following error:
the trait bound `MyIntEnum: gdnative::prelude::FromVariant` is not satisfied
required because of the requirements on the impl of `property::accessor::RawSetter<MyNode, MyIntEnum>` for `property::accessor::invalid::InvalidSetter<'_>`2
the trait bound `MyIntEnum: Export` is not satisfied
the trait `Export` is not implemented for `MyIntEnum`
This indicates that MyIntEnum
does not have the necessary traits implemented for FromVariant
and Export
. Since the default derived behavior may not be quite what we want, we can implement this with the following:
#![allow(unused)] fn main() { impl FromVariant for MyIntEnum { fn from_variant(variant: &Variant) -> Result<Self, FromVariantError> { let result = i64::from_variant(variant)?; match result { 0 => Ok(MyIntEnum::A), 1 => Ok(MyIntEnum::B), 2 => Ok(MyIntEnum::C), _ => Err(FromVariantError::UnknownEnumVariant { variant: "i64".to_owned(), expected: &["0", "1", "2"], }), } } } impl Export for MyIntEnum { type Hint = IntHint<u32>; fn export_info(_hint: Option<Self::Hint>) -> ExportInfo { Self::Hint::Enum(EnumHint::new(vec![ "A".to_owned(), "B".to_owned(), "C".to_owned(), ])) .export_info() } } }
After implementing FromVariant
and Export
, running cargo check
would result in the following additional error:
the trait bound `MyIntEnum: gdnative::prelude::ToVariant` is not satisfied
the trait `gdnative::prelude::ToVariant` is not implemented for `MyIntEnum`
If the default implementation were sufficient, we could use #[derive(ToVariant)]
for MyIntEnum
or implement it manually with the following code:
#![allow(unused)] fn main() { use gdnative::core_types::ToVariant; impl ToVariant for MyIntEnum { fn to_variant(&self) -> Variant { match self { MyIntEnum::A => { 0.to_variant() }, MyIntEnum::B => { 1.to_variant() }, MyIntEnum::C => { 2.to_variant() }, } } } }
At this point, there should be no problem in using MyIntEnum
as a property in your native class that is exported to the editor.