Skip to main content

typeuri/
lib.rs

1/*
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 * All rights reserved.
4 *
5 * This source code is licensed under the BSD-style license found in the
6 * LICENSE file in the root directory of this source tree.
7 */
8
9//! Named trait for types with globally unique type URIs.
10
11use std::any::TypeId;
12use std::collections::HashMap;
13
14// Re-export cityhasher for use in the derive macro
15pub use cityhasher;
16// Re-export dashmap so that the intern_typename macro can use $crate::dashmap
17pub use dashmap;
18// Re-export the Named derive macro from typeuri_macros
19pub use typeuri_macros::Named;
20
21/// A [`Named`] type is a type that has a globally unique name.
22pub trait Named: Sized + 'static {
23    /// The globally unique type name for the type.
24    /// This should typically be the fully qualified Rust name of the type.
25    fn typename() -> &'static str;
26
27    /// A globally unique hash for this type.
28    /// TODO: actually enforce perfect hashing
29    fn typehash() -> u64 {
30        // The `Named` macro overrides this implementation with one that
31        // memoizes the hash.
32        cityhasher::hash(Self::typename())
33    }
34
35    /// The TypeId for this type. TypeIds are unique only within a binary,
36    /// and should not be used for global identification.
37    fn typeid() -> TypeId {
38        TypeId::of::<Self>()
39    }
40
41    /// A globally unique numeric identifier for this type.
42    fn port() -> u64 {
43        Self::typehash()
44    }
45
46    /// If the named type is an enum, this returns the name of the arm
47    /// of the value self.
48    fn arm(&self) -> Option<&'static str> {
49        None
50    }
51
52    /// An unsafe version of 'arm', accepting a pointer to the value,
53    /// for use in type-erased settings.
54    ///
55    /// # Safety
56    ///
57    /// self_ must be a valid pointer to a Self instance that
58    /// remains alive for the duration of the call.
59    unsafe fn arm_unchecked(self_: *const ()) -> Option<&'static str> {
60        // SAFETY: This isn't safe. We're passing it on.
61        unsafe { &*(self_ as *const Self) }.arm()
62    }
63}
64
65macro_rules! impl_basic {
66    ($t:ty) => {
67        impl Named for $t {
68            fn typename() -> &'static str {
69                stringify!($t)
70            }
71        }
72    };
73}
74
75impl_basic!(());
76impl_basic!(bool);
77impl_basic!(i8);
78impl_basic!(u8);
79impl_basic!(i16);
80impl_basic!(u16);
81impl_basic!(i32);
82impl_basic!(u32);
83impl_basic!(i64);
84impl_basic!(u64);
85impl_basic!(i128);
86impl_basic!(u128);
87impl_basic!(isize);
88impl_basic!(usize);
89impl_basic!(f32);
90impl_basic!(f64);
91impl_basic!(String);
92impl_basic!(std::net::IpAddr);
93impl_basic!(std::net::Ipv4Addr);
94impl_basic!(std::net::Ipv6Addr);
95impl_basic!(std::time::Duration);
96impl_basic!(std::time::SystemTime);
97impl_basic!(bytes::Bytes);
98
99impl Named for &'static str {
100    fn typename() -> &'static str {
101        "&str"
102    }
103}
104
105// A macro that implements type-keyed interning of typenames. This is useful
106// for implementing [`Named`] for generic types.
107#[doc(hidden)] // not part of the public API
108#[macro_export]
109macro_rules! intern_typename {
110    ($key:ty, $format_string:expr, $($args:ty),+) => {
111        {
112            static CACHE: std::sync::LazyLock<$crate::dashmap::DashMap<std::any::TypeId, &'static str>> =
113              std::sync::LazyLock::new($crate::dashmap::DashMap::new);
114
115            // Don't use entry, because typename() might re-enter intern_typename
116            // for nested types like Option<Option<T>>
117            let typeid = std::any::TypeId::of::<$key>();
118            if let Some(value) = CACHE.get(&typeid) {
119                *value
120            } else {
121                let typename = format!($format_string, $(<$args>::typename()),+).leak();
122                CACHE.insert(typeid, typename);
123                typename
124            }
125        }
126    };
127}
128
129macro_rules! tuple_format_string {
130    ($a:ident,) => { "{}" };
131    ($a:ident, $($rest_a:ident,)+) => { concat!("{}, ", tuple_format_string!($($rest_a,)+)) };
132}
133
134macro_rules! impl_tuple_peel {
135    ($name:ident, $($other:ident,)*) => (impl_tuple! { $($other,)* })
136}
137
138macro_rules! impl_tuple {
139    () => ();
140    ( $($name:ident,)+ ) => (
141        impl<$($name:Named + 'static),+> Named for ($($name,)+) {
142            fn typename() -> &'static str {
143                intern_typename!(Self, concat!("(", tuple_format_string!($($name,)+), ")"), $($name),+)
144            }
145        }
146        impl_tuple_peel! { $($name,)+ }
147    )
148}
149
150impl_tuple! { E, D, C, B, A, Z, Y, X, W, V, U, T, }
151
152impl<T: Named + 'static> Named for Option<T> {
153    fn typename() -> &'static str {
154        intern_typename!(Self, "Option<{}>", T)
155    }
156}
157
158impl<T: Named + 'static> Named for Vec<T> {
159    fn typename() -> &'static str {
160        intern_typename!(Self, "Vec<{}>", T)
161    }
162}
163
164impl<K: Named + 'static, V: Named + 'static> Named for HashMap<K, V> {
165    fn typename() -> &'static str {
166        intern_typename!(Self, "HashMap<{}, {}>", K, V)
167    }
168}
169
170impl<T: Named + 'static, E: Named + 'static> Named for Result<T, E> {
171    fn typename() -> &'static str {
172        intern_typename!(Self, "Result<{}, {}>", T, E)
173    }
174}
175
176impl<T: Named + 'static> Named for std::ops::Range<T> {
177    fn typename() -> &'static str {
178        intern_typename!(Self, "std::ops::Range<{}>", T)
179    }
180}
181
182#[cfg(test)]
183mod tests {
184    use super::*;
185
186    #[test]
187    fn test_names() {
188        assert_eq!(String::typename(), "String");
189        assert_eq!(Option::<String>::typename(), "Option<String>");
190        assert_eq!(Vec::<String>::typename(), "Vec<String>");
191        assert_eq!(Vec::<Vec::<String>>::typename(), "Vec<Vec<String>>");
192        assert_eq!(
193            Vec::<Vec::<Vec::<String>>>::typename(),
194            "Vec<Vec<Vec<String>>>"
195        );
196        assert_eq!(
197            <(u64, String, Option::<isize>)>::typename(),
198            "(u64, String, Option<isize>)"
199        );
200    }
201
202    #[test]
203    fn test_ports() {
204        assert_eq!(String::typehash(), 3947244799002047352u64);
205        assert_eq!(String::port(), String::typehash());
206        assert_ne!(
207            Vec::<Vec::<Vec::<String>>>::typehash(),
208            Vec::<Vec::<Vec::<Vec::<String>>>>::typehash(),
209        );
210    }
211}