@@ -341,6 +341,25 @@ impl Span {
341341 pub fn primary_label < S : Into < String > > ( self , label : S ) -> LabeledSpan {
342342 LabeledSpan :: new_primary_with_span ( Some ( label. into ( ) ) , self )
343343 }
344+
345+ /// Convert [`Span`] to a single `u64`.
346+ ///
347+ /// On 64-bit platforms, `Span` is aligned on 8, so equivalent to a `u64`.
348+ /// Compiler boils this conversion down to a no-op on 64-bit platforms.
349+ /// <https://godbolt.org/z/9qv5qqd3e>
350+ ///
351+ /// Do not use this on 32-bit platforms as it's likely to be less efficient.
352+ ///
353+ /// Note: `#[ast]` macro adds `#[repr(C)]` to the struct, so field order is guaranteed.
354+ #[ expect( clippy:: inline_always) ] // Because this is a no-op on 64-bit platforms.
355+ #[ inline( always) ]
356+ fn as_u64 ( self ) -> u64 {
357+ if cfg ! ( target_endian = "little" ) {
358+ ( u64:: from ( self . end ) << 32 ) | u64:: from ( self . start )
359+ } else {
360+ ( u64:: from ( self . start ) << 32 ) | u64:: from ( self . end )
361+ }
362+ }
344363}
345364
346365impl Index < Span > for str {
@@ -378,12 +397,18 @@ impl From<Span> for LabeledSpan {
378397 }
379398}
380399
381- // Skip hashing `_align` field
400+ // Skip hashing `_align` field.
401+ // On 64-bit platforms, hash `Span` as a single `u64`, which is faster with `FxHash`.
402+ // https://godbolt.org/z/4q36xrWG8
382403impl Hash for Span {
383- #[ inline] // We exclusively use `FxHasher`, which produces small output hashing `u32`s
404+ #[ inline] // We exclusively use `FxHasher`, which produces small output hashing `u64`s and ` u32`s
384405 fn hash < H : Hasher > ( & self , hasher : & mut H ) {
385- self . start . hash ( hasher) ;
386- self . end . hash ( hasher) ;
406+ if cfg ! ( target_pointer_width = "64" ) {
407+ self . as_u64 ( ) . hash ( hasher) ;
408+ } else {
409+ self . start . hash ( hasher) ;
410+ self . end . hash ( hasher) ;
411+ }
387412 }
388413}
389414
@@ -446,7 +471,6 @@ mod test {
446471 }
447472
448473 #[ test]
449- #[ expect( clippy:: items_after_statements) ]
450474 fn test_hash ( ) {
451475 use std:: hash:: { DefaultHasher , Hash , Hasher } ;
452476 fn hash < T : Hash > ( value : T ) -> u64 {
@@ -455,20 +479,32 @@ mod test {
455479 hasher. finish ( )
456480 }
457481
458- let first_hash = hash ( Span :: new ( 0 , 5 ) ) ;
459- let second_hash = hash ( Span :: new ( 0 , 5 ) ) ;
482+ let first_hash = hash ( Span :: new ( 1 , 5 ) ) ;
483+ let second_hash = hash ( Span :: new ( 1 , 5 ) ) ;
460484 assert_eq ! ( first_hash, second_hash) ;
461485
462- // Check `_align` field does not alter hash
463- #[ derive( Hash ) ]
464- #[ repr( C ) ]
465- struct PlainSpan {
466- start : u32 ,
467- end : u32 ,
486+ // On 64-bit platforms, check hash is equivalent to `u64`
487+ #[ cfg( target_pointer_width = "64" ) ]
488+ {
489+ let u64_equivalent: u64 =
490+ if cfg ! ( target_endian = "little" ) { 1 + ( 5 << 32 ) } else { ( 1 << 32 ) + 5 } ;
491+ let u64_hash = hash ( u64_equivalent) ;
492+ assert_eq ! ( first_hash, u64_hash) ;
468493 }
469494
470- let plain_hash = hash ( PlainSpan { start : 0 , end : 5 } ) ;
471- assert_eq ! ( plain_hash, first_hash) ;
495+ // On 32-bit platforms, check `_align` field does not alter hash
496+ #[ cfg( not( target_pointer_width = "64" ) ) ]
497+ {
498+ #[ derive( Hash ) ]
499+ #[ repr( C ) ]
500+ struct PlainSpan {
501+ start : u32 ,
502+ end : u32 ,
503+ }
504+
505+ let plain_hash = hash ( PlainSpan { start : 1 , end : 5 } ) ;
506+ assert_eq ! ( first_hash, plain_hash) ;
507+ }
472508 }
473509
474510 #[ test]
0 commit comments