Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions godot-core/src/builtin/callable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -446,9 +446,13 @@ impl Callable {
InstanceId::try_from_i64(id)
}

/// Returns the 32-bit hash value of this callable's object.
///
/// _Godot equivalent: `hash`_
crate::declare_hash_u32_method! {
/// Returns the 32-bit hash value of this callable's object.
///
/// _Godot equivalent: `hash`_
}

#[deprecated = "renamed to hash_u32"]
pub fn hash(&self) -> u32 {
self.as_inner().hash().try_into().unwrap()
}
Expand Down
7 changes: 4 additions & 3 deletions godot-core/src/builtin/collections/array.rs
Original file line number Diff line number Diff line change
Expand Up @@ -285,14 +285,15 @@ impl<T: ArrayElement> Array<T> {
self.as_inner().is_empty()
}

/// Returns a 32-bit integer hash value representing the array and its contents.
crate::declare_hash_u32_method! {/// Returns a 32-bit integer hash value representing the array and its contents.
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
crate::declare_hash_u32_method! {/// Returns a 32-bit integer hash value representing the array and its contents.
crate::declare_hash_u32_method! {
/// Returns a 32-bit integer hash value representing the array and its contents.

also indentation of subsequent lines.

///
/// Note: Arrays with equal content will always produce identical hash values. However, the
/// reverse is not true. Returning identical hash values does not imply the arrays are equal,
/// because different arrays can have identical hash values due to hash collisions.
}

#[deprecated = "renamed to hash_u32"]
pub fn hash(&self) -> u32 {
// The GDExtension interface only deals in `i64`, but the engine's own `hash()` function
// actually returns `uint32_t`.
self.as_inner().hash().try_into().unwrap()
}

Expand Down
6 changes: 5 additions & 1 deletion godot-core/src/builtin/collections/dictionary.rs
Original file line number Diff line number Diff line change
Expand Up @@ -285,7 +285,11 @@ impl Dictionary {
old_value
}

/// Returns a 32-bit integer hash value representing the dictionary and its contents.
crate::declare_hash_u32_method! {
/// Returns a 32-bit integer hash value representing the dictionary and its contents.
}

#[deprecated = "renamed to hash_u32"]
#[must_use]
pub fn hash(&self) -> u32 {
self.as_inner().hash().try_into().unwrap()
Expand Down
4 changes: 3 additions & 1 deletion godot-core/src/builtin/macros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,9 @@ macro_rules! impl_builtin_traits_inner {
( Hash for $Type:ty ) => {
impl std::hash::Hash for $Type {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.hash().hash(state)
// The GDExtension interface only deals in `int64_t`, but the engine's own `hash()` function
// actually returns `uint32_t`.
self.hash_u32().hash(state)
}
}
};
Expand Down
11 changes: 11 additions & 0 deletions godot-core/src/builtin/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,17 @@ pub mod inner {
pub use crate::gen::builtin_classes::*;
}

// common hashing macro implementer
Copy link
Member

Choose a reason for hiding this comment

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

This comment could as well be removed, the macro name itself contains more information than it 🙂

#[macro_export]
macro_rules! declare_hash_u32_method {
($ ($docs:tt)+ ) => {
$( $docs )+
pub fn hash_u32(&self) -> u32 {
self.as_inner().hash().try_into().expect("Godot hashes are uint32_t")
}
}
}

// ----------------------------------------------------------------------------------------------------------------------------------------------
// Conversion functions

Expand Down
6 changes: 5 additions & 1 deletion godot-core/src/builtin/string/gstring.rs
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,11 @@ impl GString {
self.as_inner().length().try_into().unwrap()
}

/// Returns a 32-bit integer hash value representing the string.
crate::declare_hash_u32_method! {
/// Returns a 32-bit integer hash value representing the string.
}

#[deprecated = "renamed to hash_u32"]
pub fn hash(&self) -> u32 {
self.as_inner()
.hash()
Expand Down
6 changes: 5 additions & 1 deletion godot-core/src/builtin/string/node_path.rs
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,11 @@ impl NodePath {
self.get_name_count() + self.get_subname_count()
}

/// Returns a 32-bit integer hash value representing the string.
crate::declare_hash_u32_method! {
/// Returns a 32-bit integer hash value representing the string.
}

#[deprecated = "renamed to hash_u32"]
pub fn hash(&self) -> u32 {
self.as_inner()
.hash()
Expand Down
6 changes: 5 additions & 1 deletion godot-core/src/builtin/string/string_name.rs
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,11 @@ impl StringName {
self.as_inner().length() as usize
}

/// Returns a 32-bit integer hash value representing the string.
crate::declare_hash_u32_method! {
/// Returns a 32-bit integer hash value representing the string.
}

#[deprecated = "renamed to hash_u32"]
pub fn hash(&self) -> u32 {
self.as_inner()
.hash()
Expand Down
10 changes: 10 additions & 0 deletions godot-core/src/builtin/variant/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,16 @@ impl Variant {
/// Return Godot's hash value for the variant.
///
/// _Godot equivalent : `@GlobalScope.hash()`_
pub fn hash_u32(&self) -> u32 {
// @GlobalScope.hash() actually calls the VariantUtilityFunctions::hash(&Variant) function (cpp).
// This function calls the passed reference's `hash` method, which returns a uint32_t.
// Therefore, casting this function to u32 is always fine.
unsafe { interface_fn!(variant_hash)(self.var_sys()) }
.try_into()
.expect("Godot hashes are uint32_t")
}

#[deprecated = "renamed to hash_u32 and type changed to u32"]
pub fn hash(&self) -> i64 {
unsafe { interface_fn!(variant_hash)(self.var_sys()) }
}
Expand Down
2 changes: 1 addition & 1 deletion itest/rust/src/builtin_tests/containers/array_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ fn array_iter_shared() {
fn array_hash() {
let array = array![1, 2];
// Just testing that it converts successfully from i64 to u32.
array.hash();
array.hash_u32();
}

#[itest]
Expand Down
11 changes: 7 additions & 4 deletions itest/rust/src/builtin_tests/containers/callable_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,15 +72,18 @@ fn callable_validity() {
#[itest]
fn callable_hash() {
let obj = CallableTestObj::new_gd();
assert_eq!(obj.callable("assign_int"), obj.callable("assign_int")); // sanity check
assert_eq!(
obj.callable("assign_int").hash(),
obj.callable("assign_int").hash()
obj.callable("assign_int").hash_u32(),
obj.callable("assign_int").hash_u32()
);

assert_ne!(obj.callable("assign_int"), obj.callable("stringify_int")); // sanity check

// Not guaranteed, but unlikely.
assert_ne!(
obj.callable("assign_int").hash(),
obj.callable("stringify_int").hash()
obj.callable("assign_int").hash_u32(),
obj.callable("stringify_int").hash_u32()
);
}

Expand Down
16 changes: 12 additions & 4 deletions itest/rust/src/builtin_tests/containers/dictionary_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -126,15 +126,23 @@ fn dictionary_hash() {
"bar": true,
};

assert_eq!(a.hash(), b.hash(), "equal dictionaries have same hash");
assert_eq!(
a.hash_u32(),
b.hash_u32(),
"equal dictionaries have same hash"
);

assert_ne!(
a.hash(),
c.hash(),
a.hash_u32(),
c.hash_u32(),
"dictionaries with reordered content have different hash"
);

// NaNs are not equal (since Godot 4.2) but share same hash.
assert_eq!(vdict! {772: f32::NAN}.hash(), vdict! {772: f32::NAN}.hash());
assert_eq!(
vdict! {772: f32::NAN}.hash_u32(),
vdict! {772: f32::NAN}.hash_u32()
);
}

#[itest]
Expand Down
15 changes: 12 additions & 3 deletions itest/rust/src/builtin_tests/containers/variant_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -713,13 +713,22 @@ fn variant_hash() {
];

for variant in hash_is_not_0 {
assert_ne!(variant.hash(), 0)
assert_ne!(variant.hash_u32(), 0);
}
for variant in self_equal {
assert_eq!(variant.hash(), variant.hash())
assert_eq!(variant.hash_u32(), variant.hash_u32());
}

assert_eq!(Variant::nil().hash(), 0);
let v_first = 1.to_variant();
let v_second = 1.to_variant();
let v_third = "hello".to_variant();

assert_eq!(v_first, v_second);
assert_eq!(v_first.hash_u32(), v_second.hash_u32());
assert_ne!(v_first, v_third);
assert_ne!(v_first.hash_u32(), v_third.hash_u32());
Copy link
Member

@Bromeon Bromeon Oct 21, 2025

Choose a reason for hiding this comment

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

This is not guaranteed, just quite likely. The relation is:

(a == b) implies (a.hash() == b.hash())

and not

(a != b) implies (a.hash() != b.hash())

It's OK to keep this, but there should be a comment such as:

Suggested change
assert_ne!(v_first.hash_u32(), v_third.hash_u32());
assert_ne!(v_first.hash_u32(), v_third.hash_u32()); // not guaranteed, but very likely.

everywhere, also in other such tests.


assert_eq!(Variant::nil().hash_u32(), 0);

// It's not guaranteed that different object will have different hash, but it is
// extremely unlikely for a collision to happen.
Expand Down
2 changes: 2 additions & 0 deletions itest/rust/src/builtin_tests/string/gstring_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,9 @@ fn string_equality() {
let different = GString::from("some");

assert_eq!(string, second);
assert_eq!(string.hash_u32(), second.hash_u32());
assert_ne!(string, different);
assert_ne!(string.hash_u32(), different.hash_u32());
}

#[itest]
Expand Down
2 changes: 2 additions & 0 deletions itest/rust/src/builtin_tests/string/node_path_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,9 @@ fn node_path_equality() {
let different = NodePath::from("some");

assert_eq!(string, second);
assert_eq!(string.hash_u32(), second.hash_u32());
assert_ne!(string, different);
assert_ne!(string.hash_u32(), different.hash_u32());
}

#[itest]
Expand Down
2 changes: 2 additions & 0 deletions itest/rust/src/builtin_tests/string/string_name_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,9 @@ fn string_name_equality() {
let different = StringName::from("some");

assert_eq!(string, second);
assert_eq!(string.hash_u32(), second.hash_u32());
assert_ne!(string, different);
assert_ne!(string.hash_u32(), different.hash_u32());
}

#[itest]
Expand Down
Loading