11//! This module contains the implementation yew's virtual nodes' keys.
22
33use std:: fmt:: { self , Display , Formatter } ;
4- use std:: ops:: Deref ;
4+ use std:: hash:: { Hash , Hasher } ;
5+ use std:: num:: NonZeroU64 ;
56use std:: rc:: Rc ;
67
78use crate :: html:: ImplicitClone ;
89
10+ fn hash_value < H : Hash + ?Sized > ( value : & H ) -> NonZeroU64 {
11+ use std:: hash:: DefaultHasher ;
12+
13+ let mut hasher = DefaultHasher :: new ( ) ;
14+ value. hash ( & mut hasher) ;
15+ NonZeroU64 :: new ( hasher. finish ( ) ) . unwrap_or ( NonZeroU64 :: MIN )
16+ }
17+
918/// Represents the (optional) key of Yew's virtual nodes.
1019///
11- /// Keys are cheap to clone.
12- #[ derive( Clone , ImplicitClone , Debug , Ord , PartialOrd , Eq , PartialEq , Hash ) ]
20+ /// Keys are cheap to clone (a single `u64` copy) and to compare (a single
21+ /// integer comparison). Internally a key stores a hash of the value it was
22+ /// created from, so no heap allocation is required for numeric types in release
23+ /// builds.
24+ ///
25+ /// In debug builds the original string representation is kept alongside the
26+ /// hash, enabling better diagnostics.
27+ ///
28+ /// # Type-aware hashing
29+ ///
30+ /// Keys created from different types are **not** equal even when their string
31+ /// representations coincide. For example `Key::from("1")` and `Key::from(1u64)`
32+ /// are distinct.
33+ #[ derive( Clone , ImplicitClone ) ]
1334pub struct Key {
14- key : Rc < str > ,
35+ hash : NonZeroU64 ,
36+ #[ cfg( debug_assertions) ]
37+ original : Rc < str > ,
1538}
1639
1740impl Display for Key {
1841 fn fmt ( & self , f : & mut Formatter < ' _ > ) -> fmt:: Result {
19- self . key . fmt ( f)
42+ #[ cfg( debug_assertions) ]
43+ {
44+ self . original . fmt ( f)
45+ }
46+ #[ cfg( not( debug_assertions) ) ]
47+ {
48+ write ! ( f, "#{}" , self . hash)
49+ }
50+ }
51+ }
52+
53+ impl fmt:: Debug for Key {
54+ fn fmt ( & self , f : & mut Formatter < ' _ > ) -> fmt:: Result {
55+ #[ cfg( debug_assertions) ]
56+ {
57+ write ! ( f, "Key({:?})" , self . original)
58+ }
59+ #[ cfg( not( debug_assertions) ) ]
60+ {
61+ write ! ( f, "Key(#{})" , self . hash)
62+ }
63+ }
64+ }
65+
66+ impl PartialEq for Key {
67+ fn eq ( & self , other : & Self ) -> bool {
68+ self . hash == other. hash
2069 }
2170}
2271
23- impl Deref for Key {
24- type Target = str ;
72+ impl Eq for Key { }
73+
74+ impl PartialOrd for Key {
75+ fn partial_cmp ( & self , other : & Self ) -> Option < std:: cmp:: Ordering > {
76+ Some ( self . cmp ( other) )
77+ }
78+ }
2579
26- fn deref ( & self ) -> & str {
27- self . key . as_ref ( )
80+ impl Ord for Key {
81+ fn cmp ( & self , other : & Self ) -> std:: cmp:: Ordering {
82+ self . hash . cmp ( & other. hash )
83+ }
84+ }
85+
86+ impl Hash for Key {
87+ fn hash < H : Hasher > ( & self , state : & mut H ) {
88+ self . hash . hash ( state) ;
2889 }
2990}
3091
3192impl From < Rc < str > > for Key {
3293 fn from ( key : Rc < str > ) -> Self {
33- Self { key }
94+ Self {
95+ hash : hash_value ( & * key) ,
96+ #[ cfg( debug_assertions) ]
97+ original : key,
98+ }
3499 }
35100}
36101
37102impl From < & ' _ str > for Key {
38103 fn from ( key : & ' _ str ) -> Self {
39- let key: Rc < str > = Rc :: from ( key) ;
40- Self :: from ( key)
104+ Self {
105+ hash : hash_value ( key) ,
106+ #[ cfg( debug_assertions) ]
107+ original : Rc :: from ( key) ,
108+ }
41109 }
42110}
43111
@@ -47,33 +115,85 @@ impl From<String> for Key {
47115 }
48116}
49117
50- macro_rules! key_impl_from_to_string {
118+ macro_rules! key_impl_from_numeric {
51119 ( $type: ty) => {
52120 impl From <$type> for Key {
53121 fn from( key: $type) -> Self {
54- Self :: from( key. to_string( ) . as_str( ) )
122+ Self {
123+ hash: hash_value( & key) ,
124+ #[ cfg( debug_assertions) ]
125+ original: Rc :: from( key. to_string( ) . as_str( ) ) ,
126+ }
55127 }
56128 }
57129 } ;
58130}
59131
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 ) ;
132+ key_impl_from_numeric ! ( char ) ;
133+ key_impl_from_numeric ! ( u8 ) ;
134+ key_impl_from_numeric ! ( u16 ) ;
135+ key_impl_from_numeric ! ( u32 ) ;
136+ key_impl_from_numeric ! ( u64 ) ;
137+ key_impl_from_numeric ! ( u128 ) ;
138+ key_impl_from_numeric ! ( usize ) ;
139+ key_impl_from_numeric ! ( i8 ) ;
140+ key_impl_from_numeric ! ( i16 ) ;
141+ key_impl_from_numeric ! ( i32 ) ;
142+ key_impl_from_numeric ! ( i64 ) ;
143+ key_impl_from_numeric ! ( i128 ) ;
144+ key_impl_from_numeric ! ( isize ) ;
145+
146+ #[ cfg( test) ]
147+ mod tests {
148+ use super :: * ;
149+
150+ #[ test]
151+ fn same_str_equal ( ) {
152+ assert_eq ! ( Key :: from( "hello" ) , Key :: from( "hello" ) ) ;
153+ }
154+
155+ #[ test]
156+ fn different_str_not_equal ( ) {
157+ assert_ne ! ( Key :: from( "hello" ) , Key :: from( "world" ) ) ;
158+ }
159+
160+ #[ test]
161+ fn same_integer_equal ( ) {
162+ assert_eq ! ( Key :: from( 42u64 ) , Key :: from( 42u64 ) ) ;
163+ }
164+
165+ #[ test]
166+ fn different_integer_not_equal ( ) {
167+ assert_ne ! ( Key :: from( 1u64 ) , Key :: from( 2u64 ) ) ;
168+ }
169+
170+ #[ test]
171+ fn str_and_integer_not_equal ( ) {
172+ assert_ne ! ( Key :: from( "0" ) , Key :: from( 0u64 ) ) ;
173+ }
174+
175+ #[ test]
176+ fn string_and_str_equal ( ) {
177+ assert_eq ! ( Key :: from( "abc" ) , Key :: from( String :: from( "abc" ) ) ) ;
178+ }
179+
180+ #[ test]
181+ fn rc_str_and_str_equal ( ) {
182+ assert_eq ! ( Key :: from( "abc" ) , Key :: from( Rc :: <str >:: from( "abc" ) ) ) ;
183+ }
184+
185+ #[ test]
186+ fn option_key_niche_optimised ( ) {
187+ assert_eq ! (
188+ std:: mem:: size_of:: <Option <Key >>( ) ,
189+ std:: mem:: size_of:: <Key >( )
190+ ) ;
191+ }
192+ }
73193
74194#[ cfg( all( target_arch = "wasm32" , not( target_os = "wasi" ) ) ) ]
75195#[ cfg( test) ]
76- mod test {
196+ mod wasm_tests {
77197 use std:: rc:: Rc ;
78198
79199 use wasm_bindgen_test:: { wasm_bindgen_test as test, wasm_bindgen_test_configure} ;
0 commit comments