Skip to content

Conversation

Bromeon
Copy link
Member

@Bromeon Bromeon commented Aug 25, 2025

Closes #1259.

Rationale: there is no "move into" or "reuse buffer" optimization possible when converting between String, GString, StringName and NodePath. However, consuming the source string suggests that there is such a benefit.

Implementing From only on references makes this clearer and leaves the original string intact for further use.


This change has the potential to break quite a bit of code and make a few occurrences less nice to use, GString::from(&s) instead of s.into(). While I personally do like the explicitness, it's still rather wordy.

One way would be to introduce named methods to_gstring, to_string_name, to_node_path over time. Might need a new trait StringConv, as inherent methods couldn't support String, possibly one of the most common sources.

At that point, it begs the question whether we still need From between strings at all, also in light of our API design. The strings are more or less the only place where we still use From in such an extent... 🤔

Rationale: there is no "move into" or "reuse buffer" optimization possible when
converting between String, GString, StringName and NodePath. However, consuming
the source string suggests that there is such a benefit.

Implementing From only on references makes this clearer and leaves the original
string intact for further use.
@Bromeon Bromeon requested a review from ttencate August 25, 2025 22:21
@Bromeon Bromeon added quality-of-life No new functionality, but improves ergonomics/internals c: core Core components performance Performance problems and optimizations breaking-change Requires SemVer bump and removed quality-of-life No new functionality, but improves ergonomics/internals labels Aug 25, 2025
@GodotRust
Copy link

API docs are being generated and will be shortly available at: https://godot-rust.github.io/docs/gdext/pr-1286

Copy link
Contributor

@ttencate ttencate left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Red diff is best diff!

@@ -1334,7 +1334,7 @@ impl<T: ArrayElement> GodotType for Array<T> {
// Typed arrays use type hint.
PropertyHintInfo {
hint: crate::global::PropertyHint::ARRAY_TYPE,
hint_string: GString::from(element_godot_type_name::<T>()),
hint_string: GString::from(&element_godot_type_name::<T>()),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Aside: I wonder if element_godot_type_name and thus godot_type_name could/should return &str instead. Probably someone thought about this before and the answer is "no" 😅

}
}

impl From<&String> for NodePath {
fn from(s: &String) -> Self {
GString::from(s).into()
Self::from(&GString::from(s))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't like the double conversion, but I couldn't find a way in the GDExtension API to construct a NodePath directly from UTF-8 bytes. Maybe add a comment to clarify while you're here?

fn from(string_name: StringName) -> Self {
Self::from(GString::from(string_name))
fn from(s: &StringName) -> Self {
Self::from(&GString::from(s))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar here.

@ttencate
Copy link
Contributor

This change has the potential to break quite a bit of code and make a few occurrences less nice to use, GString::from(&s) instead of s.into(). While I personally do like the explicitness, it's still rather wordy.

It's also idiomatic. And s.as_str().into() also works, for those who like that better.

One way would be to introduce named methods to_gstring, to_string_name, to_node_path over time. Might need a new trait StringConv, as inherent methods couldn't support String, possibly one of the most common sources.

At that point, it begs the question whether we still need From between strings at all, also in light of our API design. The strings are more or less the only place where we still use From in such an extent... 🤔

Could do, but I think it's overkill. From may not be perfect, but it's idiomatic, well known and discoverable. It's true that .into() is less explicit than .into_T(), but sometimes you just want the behaviour of "I know these things are pretty much equivalent so I don't care about the exact types, just make it compile dammit" 😄

@Bromeon
Copy link
Member Author

Bromeon commented Aug 26, 2025

Could do, but I think it's overkill. From may not be perfect, but it's idiomatic, well known and discoverable. It's true that .into() is less explicit than .into_T(), but sometimes you just want the behaviour of "I know these things are pretty much equivalent so I don't care about the exact types, just make it compile dammit" 😄

There are two main problems I have with into():

  • It makes code harder to understand. During the implementation of this PR, I kept wondering whether hint_string would be GString or StringName, as the .into() didn't give any clue.
  • It doesn't support type inference. let a = s.into() only works if there's subsequent code requiring an unambiguous type. let a = s.to_gstring() always works.

I think there is a benefit of named conversions besides this: you can discover convertible types by just typing .to in the IDE, and it also avoids awkward solutions like GString.arg()...


Either way, I'm wondering about the migration path here. Breaking code without deprecation isn't great, and impls cannot be deprecated (rust-lang/rust#39935). Maybe we could add a compat feature for the duration of v0.4 or so...

@ttencate
Copy link
Contributor

Either way, I'm wondering about the migration path here. Breaking code without deprecation isn't great, and impls cannot be deprecated (rust-lang/rust#39935). Maybe we could add a compat feature for the duration of v0.4 or so...

The required changes are simple and fairly mechanical, as seen in this PR ­– mostly just adding & here and there, except in cases where from is passed to a higher-order function like map, where the transformation is still fairly obvious. Probably not worth adding another feature flag, which needs to be documented, tested, tracked for removal, etc. etc.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
breaking-change Requires SemVer bump c: core Core components performance Performance problems and optimizations
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Remove From impls that accept arguments by value, but actually make copies
3 participants