1- //! This module contains the implementation yew's virtual nodes' keys.
2-
31use std:: fmt:: { self , Display , Formatter } ;
4- use std:: ops:: Deref ;
2+ use std:: hash:: { Hash , Hasher } ;
3+ use std:: num:: NonZeroU64 ;
54use std:: rc:: Rc ;
65
76use crate :: html:: ImplicitClone ;
87
8+ fn hash_value < H : Hash + ?Sized > ( value : & H ) -> NonZeroU64 {
9+ use std:: hash:: DefaultHasher ;
10+
11+ let mut hasher = DefaultHasher :: new ( ) ;
12+ value. hash ( & mut hasher) ;
13+ NonZeroU64 :: new ( hasher. finish ( ) ) . unwrap_or ( NonZeroU64 :: MIN )
14+ }
15+
916/// Represents the (optional) key of Yew's virtual nodes.
1017///
11- /// Keys are cheap to clone.
12- #[ derive( Clone , ImplicitClone , Debug , Ord , PartialOrd , Eq , PartialEq , Hash ) ]
18+ /// Keys are cheap to clone (a single `u64` copy) and to compare (a single
19+ /// integer comparison). Internally a key stores a hash of the value it was
20+ /// created from, so no heap allocation is required for numeric types in release
21+ /// builds.
22+ ///
23+ /// In debug builds the original string representation is kept alongside the
24+ /// hash, enabling better diagnostics.
25+ ///
26+ /// # Type-aware hashing
27+ ///
28+ /// Keys created from different types are **not** equal even when their string
29+ /// representations coincide. For example `Key::from("1")` and `Key::from(1u64)`
30+ /// are distinct.
31+ #[ derive( Clone , ImplicitClone ) ]
1332pub struct Key {
14- key : Rc < str > ,
33+ hash : NonZeroU64 ,
34+ #[ cfg( debug_assertions) ]
35+ original : Rc < str > ,
1536}
1637
1738impl Display for Key {
1839 fn fmt ( & self , f : & mut Formatter < ' _ > ) -> fmt:: Result {
19- self . key . fmt ( f)
40+ #[ cfg( debug_assertions) ]
41+ {
42+ self . original . fmt ( f)
43+ }
44+ #[ cfg( not( debug_assertions) ) ]
45+ {
46+ write ! ( f, "#{}" , self . hash)
47+ }
2048 }
2149}
2250
23- impl Deref for Key {
24- type Target = str ;
51+ impl fmt:: Debug for Key {
52+ fn fmt ( & self , f : & mut Formatter < ' _ > ) -> fmt:: Result {
53+ #[ cfg( debug_assertions) ]
54+ {
55+ write ! ( f, "Key({:?})" , self . original)
56+ }
57+ #[ cfg( not( debug_assertions) ) ]
58+ {
59+ write ! ( f, "Key(#{})" , self . hash)
60+ }
61+ }
62+ }
2563
26- fn deref ( & self ) -> & str {
27- self . key . as_ref ( )
64+ impl PartialEq for Key {
65+ fn eq ( & self , other : & Self ) -> bool {
66+ self . hash == other. hash
67+ }
68+ }
69+
70+ impl Eq for Key { }
71+
72+ impl PartialOrd for Key {
73+ fn partial_cmp ( & self , other : & Self ) -> Option < std:: cmp:: Ordering > {
74+ Some ( self . cmp ( other) )
75+ }
76+ }
77+
78+ impl Ord for Key {
79+ fn cmp ( & self , other : & Self ) -> std:: cmp:: Ordering {
80+ self . hash . cmp ( & other. hash )
81+ }
82+ }
83+
84+ impl Hash for Key {
85+ fn hash < H : Hasher > ( & self , state : & mut H ) {
86+ self . hash . hash ( state) ;
2887 }
2988}
3089
3190impl From < Rc < str > > for Key {
3291 fn from ( key : Rc < str > ) -> Self {
33- Self { key }
92+ Self {
93+ hash : hash_value ( & * key) ,
94+ #[ cfg( debug_assertions) ]
95+ original : key,
96+ }
3497 }
3598}
3699
37100impl From < & ' _ str > for Key {
38101 fn from ( key : & ' _ str ) -> Self {
39- let key: Rc < str > = Rc :: from ( key) ;
40- Self :: from ( key)
102+ Self {
103+ hash : hash_value ( key) ,
104+ #[ cfg( debug_assertions) ]
105+ original : Rc :: from ( key) ,
106+ }
41107 }
42108}
43109
@@ -47,33 +113,85 @@ impl From<String> for Key {
47113 }
48114}
49115
50- macro_rules! key_impl_from_to_string {
116+ macro_rules! key_impl_from_numeric {
51117 ( $type: ty) => {
52118 impl From <$type> for Key {
53119 fn from( key: $type) -> Self {
54- Self :: from( key. to_string( ) . as_str( ) )
120+ Self {
121+ hash: hash_value( & key) ,
122+ #[ cfg( debug_assertions) ]
123+ original: Rc :: from( key. to_string( ) . as_str( ) ) ,
124+ }
55125 }
56126 }
57127 } ;
58128}
59129
60- key_impl_from_to_string ! ( char ) ;
61- key_impl_from_to_string ! ( u8 ) ;
62- key_impl_from_to_string ! ( u16 ) ;
63- key_impl_from_to_string ! ( u32 ) ;
64- key_impl_from_to_string ! ( u64 ) ;
65- key_impl_from_to_string ! ( u128 ) ;
66- key_impl_from_to_string ! ( usize ) ;
67- key_impl_from_to_string ! ( i8 ) ;
68- key_impl_from_to_string ! ( i16 ) ;
69- key_impl_from_to_string ! ( i32 ) ;
70- key_impl_from_to_string ! ( i64 ) ;
71- key_impl_from_to_string ! ( i128 ) ;
72- key_impl_from_to_string ! ( isize ) ;
130+ key_impl_from_numeric ! ( char ) ;
131+ key_impl_from_numeric ! ( u8 ) ;
132+ key_impl_from_numeric ! ( u16 ) ;
133+ key_impl_from_numeric ! ( u32 ) ;
134+ key_impl_from_numeric ! ( u64 ) ;
135+ key_impl_from_numeric ! ( u128 ) ;
136+ key_impl_from_numeric ! ( usize ) ;
137+ key_impl_from_numeric ! ( i8 ) ;
138+ key_impl_from_numeric ! ( i16 ) ;
139+ key_impl_from_numeric ! ( i32 ) ;
140+ key_impl_from_numeric ! ( i64 ) ;
141+ key_impl_from_numeric ! ( i128 ) ;
142+ key_impl_from_numeric ! ( isize ) ;
143+
144+ #[ cfg( test) ]
145+ mod tests {
146+ use super :: * ;
147+
148+ #[ test]
149+ fn same_str_equal ( ) {
150+ assert_eq ! ( Key :: from( "hello" ) , Key :: from( "hello" ) ) ;
151+ }
152+
153+ #[ test]
154+ fn different_str_not_equal ( ) {
155+ assert_ne ! ( Key :: from( "hello" ) , Key :: from( "world" ) ) ;
156+ }
157+
158+ #[ test]
159+ fn same_integer_equal ( ) {
160+ assert_eq ! ( Key :: from( 42u64 ) , Key :: from( 42u64 ) ) ;
161+ }
162+
163+ #[ test]
164+ fn different_integer_not_equal ( ) {
165+ assert_ne ! ( Key :: from( 1u64 ) , Key :: from( 2u64 ) ) ;
166+ }
167+
168+ #[ test]
169+ fn str_and_integer_not_equal ( ) {
170+ assert_ne ! ( Key :: from( "0" ) , Key :: from( 0u64 ) ) ;
171+ }
172+
173+ #[ test]
174+ fn string_and_str_equal ( ) {
175+ assert_eq ! ( Key :: from( "abc" ) , Key :: from( String :: from( "abc" ) ) ) ;
176+ }
177+
178+ #[ test]
179+ fn rc_str_and_str_equal ( ) {
180+ assert_eq ! ( Key :: from( "abc" ) , Key :: from( Rc :: <str >:: from( "abc" ) ) ) ;
181+ }
182+
183+ #[ test]
184+ fn option_key_niche_optimised ( ) {
185+ assert_eq ! (
186+ std:: mem:: size_of:: <Option <Key >>( ) ,
187+ std:: mem:: size_of:: <Key >( )
188+ ) ;
189+ }
190+ }
73191
74192#[ cfg( all( target_arch = "wasm32" , not( target_os = "wasi" ) ) ) ]
75193#[ cfg( test) ]
76- mod test {
194+ mod wasm_tests {
77195 use std:: rc:: Rc ;
78196
79197 use wasm_bindgen_test:: { wasm_bindgen_test as test, wasm_bindgen_test_configure} ;
0 commit comments