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