Conversation
Closed
d981ea6 to
11bff51
Compare
11bff51 to
eb042f8
Compare
Provide extra traits `AsVArg` and `DisjointVArg` (internal) to allow
`T` -> `Variant` conversions for APIs like `set("str", 123)`.
eb042f8 to
8d6b4f2
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #1450.
API changes
Tip
Generated API Docs
Added:
Dictionary<K, V>structAnyDictionarystruct#[export]Repurposed:
VarDictionaryis now an alias forDictionary<Variant, Variant>dict!macro now creates typed dictionariesUnfortunately, Godot's engine APIs almost never use typed dictionaries, and we cannot soundly assume dictionaries to be typed even in cases where we know all keys to be strings, for example. It's always possible to share a dictionary with GDScript code (or in Rust do a
Dict<K,V>->Variant->VarDictroundtrip) to insert non-K/Velements.Also, for Rust-only game logic code, there's almost never a reason to use
Dictionary;HashMapor one of the many crates are faster and more idiomatic to use.Typed dictionaries can however be useful when interfacing typed GDScript code, or when exporting properties and limiting the types that can be set from the editor. I also hope Godot's own APIs will evolve over time.
Migration
Warning
TLDR: Elements with
ToGodot::Pass != ByValuenow need to be passed by reference:I tried to retain backwards compatibility as much as possible. Unbeknownst to me, this came at massive implementation cost 😬
The TLDR is that we previously had
impl ToGodotarguments that alloweddict.set("key", 123). With the move toimpl AsArg<T>, untyped dicts would however havedict.set("key".to_variant(), 123.to_variant()).Arrays already use that approach, and I thought a lot about this.
I concluded this syntax is unacceptable, for multiple reasons.
So, decent UX would still be nice. I experimented with different approaches:
AsArg<Variant> for Tto enable implicit conversions.array![...], becausei32is now convertible to eitherAsArg<i32>orAsArg<Variant>. While such ambiguity already exists for strings and arrays, I'm not sure if that's a good change, especially as int array literals are very prevalent in our own tests.AsVArg<T>as a trait that supportsAsArg<T>plus conversion toVariant.AsArgitself, but limiting these variant conversions to dictionary (for now).AsArg<Variant>for eitherPass=ByValueorPass=ByReftypes, a known limitation in the Rust compiler. Individual impls aren't great because it won't support user-defined types like enums (which should be supported in the future).AsArg+AsVArgat the same time. To learn that a lot of Rust's native syntax like< >isn't readily usable in declarative macros without creating ambiguity, causing weird DSLs.AsVArg<T>but using a helper trait trick.Passfrom associated type to generic parameter, thus enabling rustc's trait solver to detect disjointness and allowing non-overlappingimpls.DisjointVArgtrait, which is supposed to be hidden but might leak into user code (at least in errors) -- there's probably UX to be done on this front, not today.In essence the implementation is a classic Rust trait hell, thanks to coherence being half-implemented in rustc. But it works and I think the resulting API and backwards-compatibility for at least some element types is worth it.
Adjustments are necessary for types that don't implement
ToGodot<Pass=ByValue>, for example callables (ByRef), objects (ByObject), etc. It's usually solved by prepending a&. You shouldn't need to write.to_variant()except for maybe very exotic user-defined types.Future work
There are a few more things to do, e.g.
ArrayElement->Elementget_or_nilinternally.&AnyDictionaryor&Dictionary<K, V>).Dictionary::get_or_nil-- only available forV=Variantnow, but equivalent toget().unwrap_or_default(). Useful in some cases (see tests).dict!syntax change:{ key: value }to{ key => value }(less ambiguity, no parentheses)Gd<...>elements)