Skip to main content

type_signature/
lib.rs

1#![doc = include_str!("../README.md")]
2#![no_std]
3#![cfg_attr(docsrs, feature(doc_cfg))]
4
5/// A type that can be made into a signature.
6///
7/// If implementing for a custom type, prefer to use the derive macro. Note that the derive macro
8/// requires all fields to implement this trait, as well as all generic arguments.
9///
10/// # What affects the signature
11///
12/// The signature captures the structural shape of the type. The following changes rotate
13/// the hash:
14///
15/// - Renaming the type, a field, or an enum variant (unless `#[type_signature(rename = "...")]`
16///   is applied to keep the signature's view of the name unchanged).
17/// - Adding, removing, or reordering fields or variants.
18/// - Changing a field's type.
19/// - Adding, removing, or reordering generic type parameters.
20/// - Changing the value of a const generic.
21/// - Converting between a tuple struct and a named-field struct (or equivalent changes to
22///   enum variant shapes).
23///
24/// The following changes leave the hash untouched:
25///
26/// - Field or type visibility (`pub` vs private).
27/// - Trait and method implementations on the type.
28/// - Doc comments and attributes other than `#[type_signature(...)]` (including `#[repr(..)]`
29///   attributes!).
30/// - Adding, removing, or modifying fields marked `#[type_signature(skip)]`.
31/// - Lifetime parameters and where-clause bounds.
32///
33/// # Derive attributes
34///
35/// The derive macro accepts `#[type_signature(...)]` attributes to customize what goes into
36/// the signature:
37///
38/// - `#[type_signature(rename = "...")]` on the type — use the given name in the signature
39///   instead of the type's own identifier. Useful for keeping a signature stable across a type
40///   rename, or for matching the signature of a type in another crate.
41/// - `#[type_signature(crate = "...")]` on the type — use the given path to refer to this crate
42///   (instead of the default `type-signature`), which can be useful if calling the derive macro
43///   from another crate re-exporting the trait.
44/// - `#[type_signature(rename = "...")]` on an enum variant — use the given name for this
45///   variant in the signature instead of the variant's own identifier. Useful for renaming a
46///   variant without breaking the signature.
47/// - `#[type_signature(rename = "...")]` on a field — use the given name for this field in
48///   the signature instead of the field's own identifier. Useful for renaming a field without
49///   breaking the signature.
50/// - `#[type_signature(skip)]` on a field — exclude the field from the signature. Use this for
51///   implementation-detail fields (caches, telemetry counters, `PhantomData`) whose presence
52///   shouldn't count as a breaking change to the type's observable contract.
53///
54/// ```
55/// use type_signature::TypeSignature;
56///
57/// // `rename` preserves the signature across a type rename.
58/// #[derive(TypeSignature)]
59/// struct Original {
60///     x: u32,
61/// }
62///
63/// #[derive(TypeSignature)]
64/// #[type_signature(rename = "Original")]
65/// struct Renamed {
66///     x: u32,
67/// }
68///
69/// assert_eq!(Original::CONST_HASH, Renamed::CONST_HASH);
70///
71/// // Field-level `rename` preserves the signature when a field is renamed.
72/// #[derive(TypeSignature)]
73/// struct HasFoo { foo: u32 }
74///
75/// #[derive(TypeSignature)]
76/// #[type_signature(rename = "HasFoo")]
77/// struct HasBar {
78///     #[type_signature(rename = "foo")]
79///     bar: u32,
80/// }
81///
82/// assert_eq!(HasFoo::CONST_HASH, HasBar::CONST_HASH);
83///
84/// // `skip` lets implementation-detail fields be added without changing the signature.
85/// // (Combined with `rename` here to simulate an in-place evolution of the same type.)
86/// #[derive(TypeSignature)]
87/// struct V1 {
88///     id: u32,
89/// }
90///
91/// #[derive(TypeSignature)]
92/// #[type_signature(rename = "V1")]
93/// struct V2 {
94///     id: u32,
95///     #[type_signature(skip)]
96///     cached: u64,
97/// }
98///
99/// assert_eq!(V1::CONST_HASH, V2::CONST_HASH);
100/// ```
101pub trait TypeSignature {
102    /// The signature of this type.
103    ///
104    /// For a fixed type, if the hash of this value as produced by the derive macro changes, it
105    /// will be treated as a breaking change.
106    const SIGNATURE: TypeSignatureHasher;
107
108    /// A const-available u64 value.
109    ///
110    /// For a fixed type, if this value as produced by the derive macro changes, it will be treated
111    /// as a breaking change.
112    const CONST_HASH: u64 = Self::SIGNATURE.const_hash();
113}
114
115pub use type_signature_derive::TypeSignature;
116
117/// A hashable type for generating a signature for a type.
118///
119/// The fields of this struct are not considered a stable API contract, and are not to be
120/// explicitly referenced in calling code.
121#[derive(Debug, Hash)]
122pub struct TypeSignatureHasher {
123    /// The name of the type being hashed.
124    #[doc(hidden)]
125    pub ty_name: &'static str,
126    /// The types of the generic arguments.
127    #[doc(hidden)]
128    pub ty_generics: &'static [&'static TypeSignatureHasher],
129    /// Hashes of const generics.
130    #[doc(hidden)]
131    pub const_generic_hashes: &'static [u64],
132    /// The "variants" of a type.
133    ///
134    /// For each "variant", it has the name of the variant and a list of the field names and types.
135    #[doc(hidden)]
136    pub variants: &'static [(
137        &'static str,
138        &'static [(&'static str, &'static TypeSignatureHasher)],
139    )],
140}
141impl TypeSignatureHasher {
142    /// Generate a hash at compile time.
143    ///
144    /// This function exists to cover for the inability to call [`core::hash::Hash::hash`] at const-time, and
145    /// will likely be deprecated once const traits exist.
146    #[must_use]
147    pub const fn const_hash(&self) -> u64 {
148        let mut accumulator = 0x1b61_42dc_8803_64ed;
149
150        // Mix in the name of the type
151        __macro_export::mix_values(&mut accumulator, __macro_export::hash_str(self.ty_name));
152
153        // Mix in the types of each generic. Length first so boundaries are unambiguous.
154        {
155            __macro_export::mix_values(&mut accumulator, self.ty_generics.len() as u64);
156            let mut generic_idx = 0;
157            while generic_idx < self.ty_generics.len() {
158                __macro_export::mix_values(
159                    &mut accumulator,
160                    self.ty_generics[generic_idx].const_hash(),
161                );
162                generic_idx += 1;
163            }
164        }
165
166        // Mix in each const generic argument
167        {
168            __macro_export::mix_values(&mut accumulator, self.const_generic_hashes.len() as u64);
169            let mut const_generic_idx = 0;
170            while const_generic_idx < self.const_generic_hashes.len() {
171                __macro_export::mix_values(
172                    &mut accumulator,
173                    self.const_generic_hashes[const_generic_idx],
174                );
175                const_generic_idx += 1;
176            }
177        }
178
179        // Mix in the types and names of each field.
180        {
181            __macro_export::mix_values(&mut accumulator, self.variants.len() as u64);
182            let mut variant_idx = 0;
183            while variant_idx < self.variants.len() {
184                let (variant_name, variant_fields) = self.variants[variant_idx];
185                __macro_export::mix_values(
186                    &mut accumulator,
187                    __macro_export::hash_str(variant_name),
188                );
189                __macro_export::mix_values(&mut accumulator, variant_fields.len() as u64);
190                let mut field_idx = 0;
191                while field_idx < variant_fields.len() {
192                    let (field_name, field_hasher) = variant_fields[field_idx];
193                    __macro_export::mix_values(
194                        &mut accumulator,
195                        __macro_export::hash_str(field_name),
196                    );
197                    __macro_export::mix_values(&mut accumulator, field_hasher.const_hash());
198                    field_idx += 1;
199                }
200                variant_idx += 1;
201            }
202        }
203
204        accumulator
205    }
206}
207
208/// Provide an implementation for a stdlib type.
209macro_rules! impl_for_stdlib_ty {
210    ($(
211        $stdty:ty $(where < $( $generic:ident $( : $generic_cond:tt )? ),* > )?
212    ),+ $(,)?) => {$(
213        impl$( < $( $generic $( : $generic_cond )? ),* > )? $crate::TypeSignature for $stdty {
214            const SIGNATURE: $crate::TypeSignatureHasher  = $crate::TypeSignatureHasher {
215                // `ty_name` in derive macro is just the type name, so this should avoid possible
216                // conflicts.
217                ty_name: concat!(stringify!($crate::TypeSignature), " impl for ", stringify!($stdty)),
218                ty_generics: &[
219                    $( $( &<$generic as $crate::TypeSignature>::SIGNATURE, )* )?
220                ],
221                const_generic_hashes: &[],
222                // Not formally correct, but good enough for stdlib types since they won't change
223                variants: &[],
224            };
225        }
226    )+};
227}
228
229impl_for_stdlib_ty!(
230    u8, u16, u32, usize, u64, u128,
231    i8, i16, i32, isize, i64, i128,
232    bool,
233    f32,
234    f64,
235    char,
236    str,
237    (),
238    &T where <T: TypeSignature>,
239    &mut T where <T: TypeSignature>,
240    *const T where <T: TypeSignature>,
241    *mut T where <T: TypeSignature>,
242    [T] where <T: TypeSignature>,
243    Option<T> where <T: TypeSignature> ,
244    Result<T, E> where <T: TypeSignature, E: TypeSignature>,
245    core::marker::PhantomData<T> where <T: TypeSignature>,
246    core::mem::MaybeUninit<T> where <T: TypeSignature>,
247    core::mem::ManuallyDrop<T> where <T: TypeSignature>,
248    core::net::IpAddr, core::net::Ipv4Addr, core::net::Ipv6Addr,
249    core::net::SocketAddr, core::net::SocketAddrV4, core::net::SocketAddrV6,
250    core::num::NonZeroU8, core::num::NonZeroU16, core::num::NonZeroU32, core::num::NonZeroU64, core::num::NonZeroUsize, core::num::NonZeroU128,
251    core::num::NonZeroI8, core::num::NonZeroI16, core::num::NonZeroI32, core::num::NonZeroI64, core::num::NonZeroIsize, core::num::NonZeroI128,
252    core::num::Saturating<T> where <T: TypeSignature>,
253    core::num::Wrapping<T> where <T: TypeSignature>,
254    core::ops::Range<T> where <T: TypeSignature>,
255    core::ops::RangeFrom<T> where <T: TypeSignature>,
256    core::ops::RangeFull,
257    core::ops::RangeInclusive<T> where <T: TypeSignature>,
258    core::ops::RangeTo<T> where <T: TypeSignature>,
259    core::ops::RangeToInclusive<T> where <T: TypeSignature>,
260    core::pin::Pin<T> where <T: TypeSignature>,
261    core::ptr::NonNull<T> where <T: TypeSignature>,
262    core::cmp::Ordering,
263    core::convert::Infallible,
264    core::time::Duration,
265);
266
267#[cfg(target_has_atomic = "8")]
268impl_for_stdlib_ty!(
269    core::sync::atomic::AtomicBool,
270    core::sync::atomic::AtomicI8,
271    core::sync::atomic::AtomicU8,
272);
273#[cfg(target_has_atomic = "16")]
274impl_for_stdlib_ty!(core::sync::atomic::AtomicI16, core::sync::atomic::AtomicU16);
275#[cfg(target_has_atomic = "32")]
276impl_for_stdlib_ty!(core::sync::atomic::AtomicI32, core::sync::atomic::AtomicU32);
277#[cfg(target_has_atomic = "64")]
278impl_for_stdlib_ty!(core::sync::atomic::AtomicI64, core::sync::atomic::AtomicU64);
279#[cfg(target_has_atomic = "ptr")]
280impl_for_stdlib_ty!(
281    core::sync::atomic::AtomicIsize,
282    core::sync::atomic::AtomicUsize,
283    core::sync::atomic::AtomicPtr<T> where <T: TypeSignature>,
284);
285
286impl<const N: usize, T: TypeSignature> TypeSignature for [T; N] {
287    const SIGNATURE: TypeSignatureHasher = TypeSignatureHasher {
288        ty_name: "TypeSignature impl for [T; N]",
289        ty_generics: &[&T::SIGNATURE],
290        const_generic_hashes: &[__macro_export::hash_const_usize(N)],
291        // Not formally correct, but good enough for stdlib types since they won't change
292        variants: &[],
293    };
294}
295
296/// Implement for a tuple of values which all implement [`TypeSignature`].
297macro_rules! impl_for_tuple {
298    ($(
299        ( $( $elem_ty:ident, )* $(,)? )
300    ),+ $(,)? ) => {$(
301
302        impl< $( $elem_ty ),* > TypeSignature for ($($elem_ty,)*)
303            where $( $elem_ty : TypeSignature ),*
304        {
305            const SIGNATURE: TypeSignatureHasher  = TypeSignatureHasher {
306                ty_name: concat!(stringify!($crate::TypeSignature), " impl for (", $( stringify!($elem_ty), "," ),* ),
307                ty_generics: &[
308                    $( &<$elem_ty as $crate::TypeSignature>::SIGNATURE, )*
309                ],
310                const_generic_hashes: &[],
311                // Not formally correct, but good enough for stdlib types since they won't change
312                variants: &[],
313            };
314        }
315
316    )+};
317}
318
319impl_for_tuple!(
320    (T0,),
321    (T0, T1,),
322    (T0, T1, T2,),
323    (T0, T1, T2, T3,),
324    (T0, T1, T2, T3, T4,),
325    (T0, T1, T2, T3, T4, T5,),
326    (T0, T1, T2, T3, T4, T5, T6,),
327    (T0, T1, T2, T3, T4, T5, T6, T7,),
328    (T0, T1, T2, T3, T4, T5, T6, T7, T8,),
329    (T0, T1, T2, T3, T4, T5, T6, T7, T8, T9,),
330    (T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10,),
331    (T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11,),
332    (T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12,),
333    (T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13,),
334    (
335        T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14,
336    ),
337    (
338        T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15,
339    ),
340);
341
342#[cfg(feature = "std")]
343#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
344mod std_impl {
345    extern crate std;
346
347    use crate::TypeSignature;
348
349    impl_for_stdlib_ty!(
350        std::collections::HashMap<K, V> where <K: TypeSignature, V: TypeSignature>,
351        std::collections::HashSet<T> where <T: TypeSignature>,
352        std::ffi::OsStr,
353        std::ffi::OsString,
354        std::path::Path,
355        std::path::PathBuf,
356        std::sync::Mutex<T> where <T: TypeSignature>,
357        std::sync::RwLock<T> where <T: TypeSignature>,
358        std::sync::Once,
359        std::time::Instant,
360        std::time::SystemTime,
361    );
362}
363
364#[cfg(feature = "alloc")]
365#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
366mod alloc_impl {
367    extern crate alloc;
368
369    use crate::TypeSignature;
370
371    impl_for_stdlib_ty!(
372        alloc::boxed::Box<T> where <T: TypeSignature>,
373        alloc::collections::BinaryHeap<T> where <T: TypeSignature>,
374        alloc::collections::BTreeMap<K, V> where <K: TypeSignature, V: TypeSignature>,
375        alloc::collections::BTreeSet<T> where <T: TypeSignature>,
376        alloc::collections::LinkedList<T> where <T: TypeSignature>,
377        alloc::collections::VecDeque<T> where <T: TypeSignature>,
378        alloc::ffi::CString,
379        alloc::rc::Rc<T> where <T: TypeSignature>,
380        alloc::rc::Weak<T> where <T: TypeSignature>,
381        alloc::string::String,
382        alloc::vec::Vec<T> where <T: TypeSignature>,
383    );
384
385    #[cfg(target_has_atomic = "ptr")]
386    impl_for_stdlib_ty!(
387        alloc::sync::Arc<T> where <T: TypeSignature>,
388        alloc::sync::Weak<T> where <T: TypeSignature>,
389    );
390
391    impl<'a, B: TypeSignature + alloc::borrow::ToOwned + ?Sized + 'a> TypeSignature
392        for alloc::borrow::Cow<'a, B>
393    {
394        const SIGNATURE: crate::TypeSignatureHasher = crate::TypeSignatureHasher {
395            ty_name: "TypeSignature impl for Cow<'a, B>",
396            ty_generics: &[&<B as TypeSignature>::SIGNATURE],
397            const_generic_hashes: &[],
398            variants: &[],
399        };
400    }
401}
402
403/// Implement [`TypeSignature`] for the given type as though it had the given definition.
404///
405/// You probably just want to use the derive macro, which is able to do most simple transformations
406/// by itself, but this macro may be useful in niche circumstances where the compiler-understood
407/// fields don't match the underlying shape of the type (for example, if you're bit-packing
408/// multiple flags into a single integer value).
409///
410/// Due to macro limitations, this macro requires the target type to have its name in scope at the
411/// call site, and the macro can't expand paths.
412///
413///
414/// # Example Use
415/// ```
416/// mod mod1 {
417///     use type_signature::impl_type_signature_as;
418///
419///     pub struct Foo;
420///     impl_type_signature_as! {
421///         Foo as struct { a: u32 }
422///     }
423/// }
424///
425/// mod mod2 {
426///     use type_signature::TypeSignature;
427///
428///     #[derive(TypeSignature)]
429///     pub struct Foo { a: u32 }
430/// }
431///
432/// use type_signature::TypeSignature;
433///
434/// assert_eq!(mod1::Foo::CONST_HASH, mod2::Foo::CONST_HASH);
435/// ```
436/// Or, the equivalent for enums:
437/// ```
438/// mod mod1 {
439///     use type_signature::impl_type_signature_as;
440///
441///     pub struct Foo;
442///     impl_type_signature_as! {
443///         Foo as enum { A, B(u32) }
444///     }
445/// }
446///
447/// mod mod2 {
448///     use type_signature::TypeSignature;
449///
450///     #[derive(TypeSignature)]
451///     pub enum Foo { A, B(u32) }
452/// }
453///
454/// use type_signature::TypeSignature;
455///
456/// assert_eq!(mod1::Foo::CONST_HASH, mod2::Foo::CONST_HASH);
457/// ```
458#[macro_export]
459macro_rules! impl_type_signature_as {
460    ($target:ident as struct { $( $fields:tt )* }) => {
461        $crate::_impl_ts_as_helper! { $target as $target in struct $target { $( $fields )* } }
462    };
463    ($target:ident as enum { $( $variants:tt )* }) => {
464        $crate::_impl_ts_as_helper! { $target as $target in enum $target { $( $variants )* } }
465    };
466}
467
468/// Items exported only for use in the derive macro.
469///
470/// Do not treat anything in here like a public API.
471#[doc(hidden)]
472pub mod __macro_export {
473    /// Hash a const `usize` value.
474    #[must_use]
475    pub const fn hash_const_usize(param_val: usize) -> u64 {
476        let mut accumulator = hash_str("usize");
477        mix_values(&mut accumulator, param_val as u64);
478        // Handle 128-bit targets.
479        if size_of::<usize>() == 16 {
480            mix_values(&mut accumulator, ((param_val as u128) >> 64) as u64);
481        }
482        accumulator
483    }
484
485    /// Hash a const `usize` value.
486    #[must_use]
487    pub const fn hash_const_bool(param_val: bool) -> u64 {
488        let mut accumulator = hash_str("bool");
489        mix_values(
490            &mut accumulator,
491            // Values chosen randomly to maximize number of bits different from any common pattern.
492            if param_val {
493                0x7907_e475_126f_2049
494            } else {
495                0xa656_face_e66f_d217
496            },
497        );
498        accumulator
499    }
500
501    /// Mix a `u64` in to the accumulator.
502    ///
503    /// The mixing is done to ensure that the value is highly likely to change, and will likely
504    /// be different for applying values in a different order.
505    pub const fn mix_values(accumulator: &mut u64, value: u64) {
506        // Constants are all primes, so multiplying and adding shuffles the values around
507        // isomorphically.
508        *accumulator = accumulator
509            .wrapping_mul(0x35ce_5fac_9b48_99b5)
510            .wrapping_add(0x1e5d_49b9_70ea_d075)
511            ^ value
512                .wrapping_mul(0x13fd_608d_551c_c1d1)
513                .wrapping_add(0x87b5_2407_45ca_ca0f);
514    }
515
516    /// Hash a string into a fixed `u64`.
517    ///
518    /// This function is designed to quickly jumble the contents, and result in vastly different
519    /// hashes for even subtly-different strings.
520    #[must_use]
521    pub const fn hash_str(s: &str) -> u64 {
522        let mut accumulator = 0x1124_262e_5999_d5bb;
523        mix_values(&mut accumulator, s.len() as u64);
524        let mut byte_idx = 0;
525        while byte_idx < s.len() {
526            mix_values(&mut accumulator, s.as_bytes()[byte_idx] as u64);
527            byte_idx += 1;
528        }
529        accumulator
530    }
531
532    /// Helper macro for [`crate::impl_type_signature_as`].
533    #[doc(hidden)]
534    #[macro_export]
535    macro_rules! _impl_ts_as_helper {
536        ($target:ty as $ident:ident in $item:item) => {
537            impl $crate::TypeSignature for $target {
538                const SIGNATURE: $crate::TypeSignatureHasher = {
539                    #[derive($crate::TypeSignature)]
540                    #[type_signature(crate = $crate)]
541                    #[allow(dead_code)]
542                    $item
543
544                    $ident::SIGNATURE
545                };
546            }
547        };
548    }
549}
550
551#[cfg(test)]
552mod tests {
553    use super::*;
554
555    #[derive(TypeSignature)]
556    #[type_signature(crate = super)]
557    #[allow(dead_code, reason = "Schema depends on it")]
558    struct Foo {
559        bar: u32,
560    }
561
562    #[test]
563    fn test_derive_with_custom_crate_name() {
564        assert_eq!(Foo::CONST_HASH, 0x9cb2_d1de_e1dc_8ae3);
565    }
566}