-
-
Notifications
You must be signed in to change notification settings - Fork 217
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Refactor GodotString
, NodePath
, and StringName
#233
Conversation
d2370cd
to
0111207
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice work, thanks! 😎
Added pass-by-value From impls in addition to pass-by-reference From impls, so we can do just
let node_path: NodePath = string.into();instead of needing to do
let node_path: NodePath = NodePath::from(&string);
Makes sense. However the by-value From
needlessly consumes the source, and suggests that the destination can reuse the string, which is not the case. Could you document that there is no performance benefit over From<&SourceType>
?
I would remove new()
for StringName
and NodePath
. They are rarely used if ever, redundant with Self::default()
, and definitely not the primary way to construct those types.
It's probably OK for GodotString
, for consistency with String
and since it's more meaningful to have empty strings than empty paths/interned strings.
impl fmt::Display for GodotString { | ||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | ||
let s = String::from(self); | ||
f.write_str(s.as_str()) | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could we maybe reuse chars_checked()
here?
Would save us an extra allocation.
As a bonus, it would give the obscure chars API an opportunity to be more widely tested in the real world.
/// Returns a 32-bit integer hash value representing the string. | ||
pub fn hash(&self) -> u32 { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This name may shadow the Hash::hash(&mut H)
method from the standard library. Should probably be fine though, as the trait can be invoked with fully-qualified syntax.
/* | ||
* This Source Code Form is subject to the terms of the Mozilla Public | ||
* License, v. 2.0. If a copy of the MPL was not distributed with this | ||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. | ||
*/ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should be at the top
yep
Rust's closest analogy to NodePath is probably But also the rust api guidelines say:
Nowhere there does it suggest that frequency of use, redundancy with default (in fact it explicitly says you should do it even if it is), or not being the primary way of constructing something are the relevant criteria. The only criteria is that "it is reasonable for the basic constructor to take no arguments", which in this case it is. It is reasonable to create both an empty NodePath and empty StringName with |
Thanks for the link to the guidelines! 👍 What we need to consider in our case is that we're not desigining the API from scratch -- we're merely mapping an existing Godot API to Rust. As such, we have less freedom than in the in the idealistic Rust-only world. For example, a lot of our builtin types would never have a Yet we do provide
Isn't an empty Your assessment with |
Godot's source code explicitly just always returns a null pointer for empty paths: if (p_path.is_empty()) {
return nullptr;
}
Looking at the methods of both i think it makes more sense now to not have an empty I do however think it'd make sense to have |
I'm not sure it helps clarity, it could as well have the opposite effect because there would then already be 3 syntaxes to convert strings: StringName::new(string);
StringName::from(string);
string.into(); I'm generally not a fan of having too many ways to achieve the same thing. In my eyes, APIs with narrower surface and less redundancy are easier to discover. |
True, but in this case i think not having When i try to find new methods and constructors for a type, the first thing i always check for is something named In addition, i usually find that constructors are easier to see if they are their own explicit methods and not just a trait. For one then the constructors actually show up on top of the docs, unlike trait impls that always show up far down. Having a Having an explicit constructor also signals to the user that this type can be constructed, and isn't merely a reference or something like that. Many types have And in general i dont really think constructors are very hurt by general redundancy. Once you get how a type is structured, extra constructors that dont do something very magical are generally pretty easy to understand even when they're technically redundant, as long as they're not actually identical except for the name. In this case I think as a general rule, types that can be constructed should have a I can understand how you feel about redundancy as a general sentiment, but for |
We already had this discussion for A lot of our work goes into documentation. At some point I expect that users show minimum effort toward API discovery.
I disagree that almost every type needs |
i still dont think i agree on this but I'll concede, i dont think bikeshedding this more is gonna help and i trust you. (i might not make final commit + squash today tho) |
Just wanted to add (was too slow with my edit):
The points still apply. Redundancy means:
It can be considered case-by-case, as mentioned I agree that |
…e similar where they are the same. And placing them in their own module together. Add basic tests for `StringName` and `NodePath`. Add `Hash` impl using `InnerX::hash` for all, instead of just `StringName`. Add `new` constructors to all the string types. Add `as_inner` functions to all the string types. Add conversions between all the string types. Add pass-by-value conversions where there used to only be pass-by-reference conversions. Add `VariantMetadata` impl for `String`
0111207
to
4c79b2b
Compare
bors try |
tryBuild succeeded: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One last thing, you can merge once that's ready 🙂
impl<S> From<S> for $Ty | ||
where | ||
S: AsRef<str>, | ||
{ | ||
fn from(string: S) -> Self { | ||
let intermediate = GodotString::from(string.as_ref()); | ||
Self::from(&intermediate) | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In the case of $Ty == GodotString
, this would first call
GodotString::from(&str)
and then
GodotString::from(&GodotString)
Which trait impl powers the 2nd conversion? There is no From<&GodotString> for GodotString
. While From
is reflexive, it shouldn't be for references...?
The background behind my question is that it might perform an unnecessary conversion/copy.
Could you implement from()
directly like this?
fn from(string: S) -> Self {
// note: no &
Self::from(GodotString::from(string.as_ref()));
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't use this macro for GodotString
, precisely because this macro relies on there already existing a From
conversion for GodotString
and String
. it's only used for StringName
and NodePath
, because their conversions are identical. But GodotString
is a special case.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, I see. In that case you can go ahead! 🙂
bors r+ |
Build succeeded: |
Creates a new module in
builtin
that contains all the string-related modules.Make all the string-files look more similar where it made sense. And add things that only existed in one of the three but not the others, such as:
Hash
impl (now using godot's hashing), new-constructor, conversions.Added pass-by-value From impls in addition to pass-by-reference From impls, so we can do just
instead of needing to do
Moves
String
-specific stuff intobuiltin/string/mod.rs
and renamedstring.rs
togodot_string.rs
. And adds aVariantMetadata
impl forString
.Adds some more tests to test all the types a bit more extensively.
Since this is gonna conflict with #231 (as i added some stuff to StringName there) i wanna wait with merging this until that is merged. but otherwise the PR is ready to be merged.