Skip to main content
This is unreleased documentation for Yew Next version.
For up-to-date documentation, see the latest version on docs.rs.

yew/virtual_dom/
key.rs

1//! This module contains the implementation yew's virtual nodes' keys.
2
3use std::fmt::{self, Display, Formatter};
4use std::hash::{Hash, Hasher};
5use std::num::NonZeroU64;
6use std::rc::Rc;
7
8use crate::html::ImplicitClone;
9
10fn 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
18/// Represents the (optional) key of Yew's virtual nodes.
19///
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)]
34pub struct Key {
35    hash: NonZeroU64,
36    #[cfg(debug_assertions)]
37    original: Rc<str>,
38}
39
40impl Display for Key {
41    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
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
53impl 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
66impl PartialEq for Key {
67    fn eq(&self, other: &Self) -> bool {
68        self.hash == other.hash
69    }
70}
71
72impl Eq for Key {}
73
74impl PartialOrd for Key {
75    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
76        Some(self.cmp(other))
77    }
78}
79
80impl Ord for Key {
81    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
82        self.hash.cmp(&other.hash)
83    }
84}
85
86impl Hash for Key {
87    fn hash<H: Hasher>(&self, state: &mut H) {
88        self.hash.hash(state);
89    }
90}
91
92impl From<Rc<str>> for Key {
93    fn from(key: Rc<str>) -> Self {
94        Self {
95            hash: hash_value(&*key),
96            #[cfg(debug_assertions)]
97            original: key,
98        }
99    }
100}
101
102impl From<&'_ str> for Key {
103    fn from(key: &'_ str) -> Self {
104        Self {
105            hash: hash_value(key),
106            #[cfg(debug_assertions)]
107            original: Rc::from(key),
108        }
109    }
110}
111
112impl From<String> for Key {
113    fn from(key: String) -> Self {
114        Self::from(key.as_str())
115    }
116}
117
118macro_rules! key_impl_from_numeric {
119    ($type:ty) => {
120        impl From<$type> for Key {
121            fn from(key: $type) -> Self {
122                Self {
123                    hash: hash_value(&key),
124                    #[cfg(debug_assertions)]
125                    original: Rc::from(key.to_string().as_str()),
126                }
127            }
128        }
129    };
130}
131
132key_impl_from_numeric!(char);
133key_impl_from_numeric!(u8);
134key_impl_from_numeric!(u16);
135key_impl_from_numeric!(u32);
136key_impl_from_numeric!(u64);
137key_impl_from_numeric!(u128);
138key_impl_from_numeric!(usize);
139key_impl_from_numeric!(i8);
140key_impl_from_numeric!(i16);
141key_impl_from_numeric!(i32);
142key_impl_from_numeric!(i64);
143key_impl_from_numeric!(i128);
144key_impl_from_numeric!(isize);
145
146#[cfg(test)]
147mod 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}
193
194#[cfg(all(target_arch = "wasm32", not(target_os = "wasi")))]
195#[cfg(test)]
196mod wasm_tests {
197    use std::rc::Rc;
198
199    use wasm_bindgen_test::{wasm_bindgen_test as test, wasm_bindgen_test_configure};
200
201    use crate::html;
202
203    wasm_bindgen_test_configure!(run_in_browser);
204
205    #[test]
206    fn all_key_conversions() {
207        let _ = html! {
208            <key="string literal">
209                <img key={"String".to_owned()} />
210                <p key={Rc::<str>::from("rc")}></p>
211                <key='a'>
212                    <p key=11_usize></p>
213                    <p key=12_u8></p>
214                    <p key=13_u16></p>
215                    <p key=14_u32></p>
216                    <p key=15_u64></p>
217                    <p key=16_u128></p>
218                    <p key=21_isize></p>
219                    <p key=22_i8></p>
220                    <p key=23_i16></p>
221                    <p key=24_i32></p>
222                    <p key=25_i128></p>
223                </>
224            </>
225        };
226    }
227}