-
Notifications
You must be signed in to change notification settings - Fork 210
Description
There are multiple ways to convert Variant
into certain types:
// Do a best effort to create an i64 out of the variant, return default value if type is not convertible
fn Variant::to_i64(&self) -> i64;
// Returns Some(i64) if this variant has exactly type i64, None otherwise.
fn Variant::try_to_i64(&self) -> Option<i64>;
// Returns Ok(i64) if this variant has exactly type i64, Err otherwise
fn i64::from_variant(variant: &Variant) -> Result<Self, FromVariantError>;
// Match the variant's dispatch enum
if let VariantDispatch::I64(value) = variant.dispatch() { ... }
and back:
fn Variant::from_i64(v: i64) -> Variant;
fn i64::to_variant(&self) -> Variant;
fn i64::owned_to_variant(self) -> Variant;
Implementation (after cargo expand)
impl Variant {
pub fn from_i64(v: i64) -> Variant {
unsafe {
let api = get_api();
let mut dest = sys::godot_variant::default();
(api.godot_variant_new_int)(&mut dest, v);
Variant(dest)
}
}
pub fn to_i64(&self) -> I64 {
unsafe {
#[allow(clippy::useless_transmute)]
transmute((get_api().godot_variant_as_int)(&self.0))
}
}
pub fn try_to_i64(&self) -> Option<I64> {
i64::from_variant(self).ok()
}
// Helper method
fn try_as_sys_of_type(
&self,
expected: VariantType,
) -> Result<&sys::godot_variant, FromVariantError> {
let variant_type = self.get_type();
if variant_type != expected {
return Err(...);
}
Ok(&self.0) // self.0 is the sys handle
}
}
impl<T: ToVariant> OwnedToVariant for T {
#[inline]
fn owned_to_variant(self) -> Variant {
self.to_variant()
}
}
impl ToVariantEq for i64 {}
impl ToVariant for i64 {
#[inline]
fn to_variant(&self) -> Variant {
Variant::from_i64(*self)
}
}
impl FromVariant for i64 {
#[inline]
fn from_variant(variant: &Variant) -> Result<Self, FromVariantError> {
variant
.try_as_sys_of_type(VariantType::I64)
.map(|v| unsafe { (get_api().godot_variant_as_int)(v) })
}
}
impl ToVariantEq for i32 {}
impl ToVariant for i32 {
#[inline]
fn to_variant(&self) -> Variant {
((*self) as i64).to_variant()
}
}
impl FromVariant for i32 {
#[inline]
fn from_variant(variant: &Variant) -> Result<Self, FromVariantError> {
<i64>::from_variant(variant).map(|i| i as Self)
}
}
Current behavior
Mostly the conversion to types is interesting.
There are two behaviors:
to_i64()
-- use GDNative APIgodot_variant_as_int
, emulating GDScript implicit conversionstry_to_i64()
,i64::from_variant()
-- only convert if the variant indeed has exactly this type
In Rust being a strongly typed language with explicit conversions, case 1. can sometimes be surprising, especially if a conversion silently fails and an arbitrary default value is chosen. A while ago I tested how values constructed in GDScript are converted using Rust Variant
's conversion methods:
These are the good cases and more or less expected, but it gets problematic when a type is not representable in the target type, and a default like 0
, false
, ""
etc. is chosen (TODO: include those tests).
Possible changes
My suggestion is to rename try_to_*()
methods to to_*()
, so the most straightforward way to convert returns an Option
, making the caller aware of potential errors and handling them explicitly.
For the existing to_*()
methods, we have multiple options:
- remove them, if they are deemed more dangerous than helpful (this might limit some valid use cases, like
float
->int
conversion) - keep them as is, but name them more explicitly, e.g.
coerce_to_*()
orto_*_or_default()
- name them something like
convert_to_*()
, change return type toOption<T>
and only returnSome(value)
on meaningful conversions. That would meanString("3.2")
->f32(3.2)
would be OK, butString("3.2k")
->f32(0.0)
would not happen. This will likely need a lot of effort and checking cases manually. - rethink the entire "direct conversion API" in the presence of the
VariantDispatch
enum
Independently of what we end up with, we should improve documentation for Variant
:
- list each possibility of conversion from/to variants, with their respective use cases
- explain which mechanism the implicit conversion for
#[export]
method parameters and return types uses - elabore new
VariantDispatch
API and recommend its usage withmatch
andif let