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    /// The globally unique port for this type. Typed ports are in the range
42    /// of 1<<63..1<<64-1.
43    fn port() -> u64 {
44        Self::typehash() | (1 << 63)
45    }
46
47    /// If the named type is an enum, this returns the name of the arm
48    /// of the value self.
49    fn arm(&self) -> Option<&'static str> {
50        None
51    }
52
53    /// An unsafe version of 'arm', accepting a pointer to the value,
54    /// for use in type-erased settings.
55    unsafe fn arm_unchecked(self_: *const ()) -> Option<&'static str> {
56        // SAFETY: This isn't safe. We're passing it on.
57        unsafe { &*(self_ as *const Self) }.arm()
58    }
59}
60
61macro_rules! impl_basic {
62    ($t:ty) => {
63        impl Named for $t {
64            fn typename() -> &'static str {
65                stringify!($t)
66            }
67        }
68    };
69}
70
71impl_basic!(());
72impl_basic!(bool);
73impl_basic!(i8);
74impl_basic!(u8);
75impl_basic!(i16);
76impl_basic!(u16);
77impl_basic!(i32);
78impl_basic!(u32);
79impl_basic!(i64);
80impl_basic!(u64);
81impl_basic!(i128);
82impl_basic!(u128);
83impl_basic!(isize);
84impl_basic!(usize);
85impl_basic!(f32);
86impl_basic!(f64);
87impl_basic!(String);
88impl_basic!(std::net::IpAddr);
89impl_basic!(std::net::Ipv4Addr);
90impl_basic!(std::net::Ipv6Addr);
91impl_basic!(std::time::Duration);
92impl_basic!(std::time::SystemTime);
93impl_basic!(bytes::Bytes);
94
95impl Named for &'static str {
96    fn typename() -> &'static str {
97        "&str"
98    }
99}
100
101// A macro that implements type-keyed interning of typenames. This is useful
102// for implementing [`Named`] for generic types.
103#[doc(hidden)] // not part of the public API
104#[macro_export]
105macro_rules! intern_typename {
106    ($key:ty, $format_string:expr, $($args:ty),+) => {
107        {
108            static CACHE: std::sync::LazyLock<$crate::dashmap::DashMap<std::any::TypeId, &'static str>> =
109              std::sync::LazyLock::new($crate::dashmap::DashMap::new);
110
111            // Don't use entry, because typename() might re-enter intern_typename
112            // for nested types like Option<Option<T>>
113            let typeid = std::any::TypeId::of::<$key>();
114            if let Some(value) = CACHE.get(&typeid) {
115                *value
116            } else {
117                let typename = format!($format_string, $(<$args>::typename()),+).leak();
118                CACHE.insert(typeid, typename);
119                typename
120            }
121        }
122    };
123}
124
125macro_rules! tuple_format_string {
126    ($a:ident,) => { "{}" };
127    ($a:ident, $($rest_a:ident,)+) => { concat!("{}, ", tuple_format_string!($($rest_a,)+)) };
128}
129
130macro_rules! impl_tuple_peel {
131    ($name:ident, $($other:ident,)*) => (impl_tuple! { $($other,)* })
132}
133
134macro_rules! impl_tuple {
135    () => ();
136    ( $($name:ident,)+ ) => (
137        impl<$($name:Named + 'static),+> Named for ($($name,)+) {
138            fn typename() -> &'static str {
139                intern_typename!(Self, concat!("(", tuple_format_string!($($name,)+), ")"), $($name),+)
140            }
141        }
142        impl_tuple_peel! { $($name,)+ }
143    )
144}
145
146impl_tuple! { E, D, C, B, A, Z, Y, X, W, V, U, T, }
147
148impl<T: Named + 'static> Named for Option<T> {
149    fn typename() -> &'static str {
150        intern_typename!(Self, "Option<{}>", T)
151    }
152}
153
154impl<T: Named + 'static> Named for Vec<T> {
155    fn typename() -> &'static str {
156        intern_typename!(Self, "Vec<{}>", T)
157    }
158}
159
160impl<K: Named + 'static, V: Named + 'static> Named for HashMap<K, V> {
161    fn typename() -> &'static str {
162        intern_typename!(Self, "HashMap<{}, {}>", K, V)
163    }
164}
165
166impl<T: Named + 'static, E: Named + 'static> Named for Result<T, E> {
167    fn typename() -> &'static str {
168        intern_typename!(Self, "Result<{}, {}>", T, E)
169    }
170}
171
172impl<T: Named + 'static> Named for std::ops::Range<T> {
173    fn typename() -> &'static str {
174        intern_typename!(Self, "std::ops::Range<{}>", T)
175    }
176}
177
178#[cfg(test)]
179mod tests {
180    use super::*;
181
182    #[test]
183    fn test_names() {
184        assert_eq!(String::typename(), "String");
185        assert_eq!(Option::<String>::typename(), "Option<String>");
186        assert_eq!(Vec::<String>::typename(), "Vec<String>");
187        assert_eq!(Vec::<Vec::<String>>::typename(), "Vec<Vec<String>>");
188        assert_eq!(
189            Vec::<Vec::<Vec::<String>>>::typename(),
190            "Vec<Vec<Vec<String>>>"
191        );
192        assert_eq!(
193            <(u64, String, Option::<isize>)>::typename(),
194            "(u64, String, Option<isize>)"
195        );
196    }
197
198    #[test]
199    fn test_ports() {
200        assert_eq!(String::typehash(), 3947244799002047352u64);
201        assert_eq!(String::port(), 13170616835856823160u64);
202        assert_ne!(
203            Vec::<Vec::<Vec::<String>>>::typehash(),
204            Vec::<Vec::<Vec::<Vec::<String>>>>::typehash(),
205        );
206    }
207}