fixed_type_id/
lib.rs

1#![deny(missing_docs)]
2#![allow(internal_features)]
3#![allow(incomplete_features)]
4#![feature(str_from_raw_parts)]
5#![feature(generic_const_exprs)]
6#![feature(nonzero_internals)]
7#![cfg_attr(feature = "specialization", feature(specialization))]
8#![doc = include_str!("../README.md")]
9
10mod remote_impl;
11
12use core::fmt;
13use std::hash::Hash;
14
15/// Prelude used with [`fixed_type_id`]
16pub mod prelude {
17    pub use super::fixed_type_id;
18    pub use super::{fstr_to_str, ConstTypeName, FixedId, FixedTypeId, FixedVersion};
19}
20
21pub use fixed_type_id_macros::fixed_type_id;
22use semver::Version;
23
24/// The length of the type name, can be configured by feature flags `len128`, `len64` and `len256`, the default is `len128`.
25#[cfg(feature = "len128")]
26pub const CONST_TYPENAME_LEN: usize = 128;
27
28/// The length of the type name, can be configured by feature flags `len128`, `len64` and `len256`, the default is `len128`.
29#[cfg(feature = "len64")]
30pub const CONST_TYPENAME_LEN: usize = 64;
31
32/// The length of the type name, can be configured by feature flags `len128`, `len64` and `len256`, the default is `len128`.
33#[cfg(feature = "len256")]
34pub const CONST_TYPENAME_LEN: usize = 256;
35
36/// A strong type for type id.
37#[repr(transparent)]
38#[cfg_attr(feature = "serde", derive(::serde::Serialize, ::serde::Deserialize))]
39#[cfg_attr(
40    feature = "rkyv",
41    derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)
42)]
43#[cfg_attr(feature = "rkyv", rkyv(attr(allow(missing_docs))))]
44#[cfg_attr(feature = "rkyv", rkyv(compare(PartialEq), derive(Debug)))]
45#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
46pub struct FixedId(pub u64);
47
48#[cfg(feature = "rkyv")]
49impl From<&ArchivedFixedId> for FixedId {
50    fn from(value: &ArchivedFixedId) -> Self {
51        FixedId(value.0.into())
52    }
53}
54
55/// Just write internal [`u64`] with [`std::hash::Hasher::write_u64`].
56impl Hash for FixedId {
57    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
58        state.write_u64(self.0);
59    }
60}
61
62impl FixedId {
63    /// Get UniqueId of a type
64    pub const fn from<Target: 'static + ?Sized + FixedTypeId>() -> Self {
65        Target::TYPE_ID
66    }
67
68    /// Get the inner u64 value.
69    pub const fn as_u64(&self) -> u64 {
70        self.0
71    }
72
73    /// Get UniqueId from a type name.
74    ///
75    /// It can be used inside const context.
76    pub const fn from_type_name(type_name: &'static str, version: Option<FixedVersion>) -> Self {
77        let hash = match version {
78            None => rapidhash::rapidhash(type_name.as_bytes()),
79            Some(version) => {
80                // first hash the typename, get a base hash
81                let name_hash = rapidhash::rapidhash(type_name.as_bytes());
82                // then hash the version
83                let version_hash = rapidhash::rapidhash(&version.const_to_bytes());
84                // then combine name_hash and version_hash as a new `&[u8]`
85                //
86                // rapidhash::rapidhash(&u64s_to_bytes(&[name_hash, version_hash]));
87                //
88                // or use rapid_mix
89                rapid_mix(name_hash, version_hash)
90            }
91        };
92        FixedId(hash)
93    }
94}
95
96const fn u64s_to_bytes<const N: usize>(slice: &[u64; N]) -> [u8; N * 8] {
97    let mut bytes = [0u8; N * 8];
98
99    let mut slice_remaining: &[u64] = slice;
100    let mut i = 0;
101    let mut slice_index = 0;
102    while let [current, tail @ ..] = slice_remaining {
103        let mut current_bytes: &[u8] = &current.to_le_bytes();
104        while let [current, tail @ ..] = current_bytes {
105            bytes[i] = *current;
106            i += 1;
107            current_bytes = tail;
108        }
109        slice_index += 1;
110        debug_assert!(i == 8 * slice_index);
111        slice_remaining = tail;
112    }
113
114    bytes
115}
116
117/// for n <= 32, returns a static string
118/// for n > 32, returns "N"
119/// for special usize, eg 64, 128, 256, 512, 768, 1024, 2048, 4096, 8192, 16384, 32768, 65536, returns a static string
120pub const fn usize_to_str(n: usize) -> &'static str {
121    match n {
122        0 => "0",
123        1 => "1",
124        2 => "2",
125        3 => "3",
126        4 => "4",
127        5 => "5",
128        6 => "6",
129        7 => "7",
130        8 => "8",
131        9 => "9",
132        10 => "10",
133        11 => "11",
134        12 => "12",
135        13 => "13",
136        14 => "14",
137        15 => "15",
138        16 => "16",
139        17 => "17",
140        18 => "18",
141        19 => "19",
142        20 => "20",
143        21 => "21",
144        22 => "22",
145        23 => "23",
146        24 => "24",
147        25 => "25",
148        26 => "26",
149        27 => "27",
150        28 => "28",
151        29 => "29",
152        30 => "30",
153        31 => "31",
154        32 => "32",
155        64 => "64",
156        128 => "128",
157        256 => "256",
158        512 => "512",
159        768 => "768",
160        1024 => "1024",
161        2048 => "2048",
162        4096 => "4096",
163        8192 => "8192",
164        16384 => "16384",
165        32768 => "32768",
166        65536 => "65536",
167        _ => "N",
168    }
169}
170
171/// Copy from [`rapidhash`]
172#[inline(always)]
173const fn rapid_mum(a: u64, b: u64) -> (u64, u64) {
174    let r = a as u128 * b as u128;
175    (r as u64, (r >> 64) as u64)
176}
177
178/// Copy from [`rapidhash`]
179#[inline(always)]
180const fn rapid_mix(a: u64, b: u64) -> u64 {
181    let (a, b) = rapid_mum(a, b);
182    a ^ b
183}
184
185impl fmt::Display for FixedId {
186    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
187        write!(f, "{}", self.0)
188    }
189}
190
191/// A trait for providing a type id number.
192pub trait FixedTypeId {
193    /// The type name defined by the user, more unique and stable name than the [`core::any::type_name`]
194    ///
195    /// When enabled feature `erase_name`, the type name will be a hex string of the hash of the original type name if it's a primitive type without generic.
196    /// Otherwise, it will be the original type name.
197    ///
198    /// You should implement this trait as specific as possible. Because that the generic implement will make your binary size larger.
199    const TYPE_NAME: &'static str;
200    /// A unique id for a type.
201    ///
202    /// It's default use [`FixedId::from_type_name`] with [`Self::TYPE_VERSION`] as additional parameter.
203    /// When you want to define an id without version, you can use [`FixedId::from_type_name`] without additional version parameter.
204    const TYPE_ID: FixedId = FixedId::from_type_name(Self::TYPE_NAME, Some(Self::TYPE_VERSION));
205    /// A semver for a type, with out pre release, build meta etc.
206    ///
207    /// Used to check version compatibility. If versions are not compatible, it can be cast to an semver.
208    const TYPE_VERSION: FixedVersion = FixedVersion::new(0, 0, 0);
209
210    /// Returns the type name.
211    #[inline(always)]
212    fn ty_name(&self) -> &'static str {
213        Self::TYPE_NAME
214    }
215
216    /// Returns the type id number.
217    #[inline(always)]
218    fn ty_id(&self) -> FixedId {
219        Self::TYPE_ID
220    }
221
222    /// Returns the version for a type
223    #[inline(always)]
224    fn ty_version(&self) -> FixedVersion {
225        Self::TYPE_VERSION
226    }
227}
228
229/// A semver for a type, but without pre release, build meta etc.
230#[cfg_attr(feature = "serde", derive(::serde::Serialize, ::serde::Deserialize))]
231#[cfg_attr(
232    feature = "rkyv",
233    derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)
234)]
235#[cfg_attr(feature = "rkyv", rkyv(attr(allow(missing_docs))))]
236#[cfg_attr(feature = "rkyv", rkyv(compare(PartialEq), derive(Debug)))]
237#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
238pub struct FixedVersion {
239    /// The major version number.
240    pub major: u64,
241    /// The minor version number.
242    pub minor: u64,
243    /// The patch version number.
244    pub patch: u64,
245}
246
247impl FixedVersion {
248    /// Create a new `FixedVersion`
249    #[inline(always)]
250    pub const fn new(major: u64, minor: u64, patch: u64) -> Self {
251        FixedVersion {
252            major,
253            minor,
254            patch,
255        }
256    }
257
258    /// Get the bytes of the version, can be used in const context.
259    ///
260    /// It's slower than [`as_bytes`], but can be used in const context.
261    pub const fn const_to_bytes(&self) -> [u8; 24] {
262        u64s_to_bytes(&[self.major, self.minor, self.patch])
263    }
264
265    /// Get the bytes presentation of the version, as a `[u8; 24]`
266    pub fn to_bytes(&self) -> [u8; 24] {
267        let mut bytes = [0u8; 24];
268        bytes[0..8].copy_from_slice(&self.major.to_le_bytes());
269        bytes[8..16].copy_from_slice(&self.minor.to_le_bytes());
270        bytes[16..24].copy_from_slice(&self.patch.to_le_bytes());
271        bytes
272    }
273
274    /// If a [`FixedVersion`] compatible with another [`FixedVersion`]
275    pub fn is_compatible(&self, expected_version: &FixedVersion) -> bool {
276        let compatible_cmp = semver::Comparator {
277            op: semver::Op::Caret,
278            major: expected_version.major,
279            minor: Some(expected_version.minor),
280            patch: Some(expected_version.patch),
281            pre: semver::Prerelease::EMPTY,
282        };
283        compatible_cmp.matches(&Version::new(self.major, self.minor, self.patch))
284    }
285
286    /// If a [`FixedVersion`] matches a [`semver::Comparator`]?
287    pub fn matches(&self, comparator: &semver::Comparator) -> bool {
288        comparator.matches(&Version::new(self.major, self.minor, self.patch))
289    }
290}
291
292impl From<(u64, u64, u64)> for FixedVersion {
293    fn from(value: (u64, u64, u64)) -> Self {
294        FixedVersion::new(value.0, value.1, value.2)
295    }
296}
297
298impl From<FixedVersion> for (u64, u64, u64) {
299    fn from(value: FixedVersion) -> Self {
300        (value.major, value.minor, value.patch)
301    }
302}
303
304impl From<Version> for FixedVersion {
305    fn from(value: Version) -> Self {
306        FixedVersion::new(value.major, value.minor, value.patch)
307    }
308}
309
310impl From<FixedVersion> for Version {
311    fn from(value: FixedVersion) -> Self {
312        Version::new(value.major, value.minor, value.patch)
313    }
314}
315
316/// Get the hash from a type name and version, use the same procedure as [`FixedId::from_type_name`], but better performance.
317///
318/// It can't be used in const context.
319pub fn name_version_to_hash(name: &str, version: &FixedVersion) -> u64 {
320    let name_hash = rapidhash::rapidhash(name.as_bytes());
321    // let version_hash = rapidhash::rapidhash(&version.as_bytes());
322    let mut bytes = [0u8; 24];
323    bytes[0..8].copy_from_slice(&version.major.to_le_bytes());
324    bytes[8..16].copy_from_slice(&version.minor.to_le_bytes());
325    bytes[16..24].copy_from_slice(&version.patch.to_le_bytes());
326    rapid_mix(name_hash, rapidhash::rapidhash(&bytes))
327}
328
329/// A trait for providing a const fixed string for the type name, used to avoid heap when need to format the type name.
330///
331/// Useful for types with generic parameters. the size of the type name is limited by `CONST_TYPENAME_LEN`, which can be
332/// configured by feature flags `len128`, `len64` and `len256`, the default is `len128`.
333///
334/// But note that implementing this trait for a lot of types will make your binary size larger,
335/// and slow down your compile time.
336///
337/// ## Example
338///
339/// ```rust
340/// # #![cfg_attr(feature = "specialization", feature(specialization))]
341/// use fixed_type_id::prelude::*;
342/// pub struct A<T> {
343///     pub t: T,
344/// }
345///
346/// impl<T: FixedTypeId> FixedTypeId for A<T> {
347///     const TYPE_NAME: &'static str = fstr_to_str(&Self::TYPE_NAME_FSTR);
348/// }
349///
350/// impl<T: FixedTypeId> ConstTypeName for A<T> {
351///     const RAW_SLICE: &[&str] = &["A", "<", T::TYPE_NAME, ">"];
352/// }
353///
354/// assert_eq!(<A<u8> as FixedTypeId>::TYPE_NAME, "A<u8>");
355/// ```
356pub trait ConstTypeName {
357    /// A raw slice for the type name, used to create a fixed `fstr`.
358    ///
359    /// It's the only const you should defined for your struct.
360    const RAW_SLICE: &[&str];
361    /// A fixed string for the type name, used to avoid heap when need to format the type name.
362    const TYPE_NAME_FSTR: fixedstr_ext::fstr<CONST_TYPENAME_LEN> = slice_to_fstr(Self::RAW_SLICE);
363}
364
365/// A helper function to get the type name of a type.
366#[inline(always)]
367pub fn type_name<T: ?Sized + FixedTypeId>() -> &'static str {
368    T::TYPE_NAME
369}
370
371/// A helper function to get the type id of a type.
372#[inline(always)]
373pub fn type_id<T: ?Sized + FixedTypeId>() -> FixedId {
374    T::TYPE_ID
375}
376
377/// A helper function to get the version of a type.
378#[inline(always)]
379pub fn type_version<T: ?Sized + FixedTypeId>() -> FixedVersion {
380    T::TYPE_VERSION
381}
382
383/// Helper function to convert a fixed string [`fixedstr_ext::fstr`] to a string.
384pub const fn fstr_to_str<const N: usize>(fstr: &'static fixedstr_ext::fstr<N>) -> &'static str {
385    unsafe { core::str::from_raw_parts(fstr.to_ptr(), fstr.len()) }
386}
387
388/// Helper function to convert a slice of string to a fixed string [`fixedstr_ext::fstr`].
389pub const fn slice_to_fstr<const N: usize>(slice: &[&str]) -> fixedstr_ext::fstr<N> {
390    fixedstr_ext::fstr::<N>::const_create_from_str_slice(slice)
391}
392
393#[cfg(feature = "specialization")]
394impl<T> FixedTypeId for T {
395    default const TYPE_NAME: &'static str = "NOT_IMPLEMENTED";
396
397    default const TYPE_ID: FixedId =
398        FixedId::from_type_name(Self::TYPE_NAME, Some(Self::TYPE_VERSION));
399
400    default const TYPE_VERSION: FixedVersion = FixedVersion::new(0, 0, 0);
401
402    default fn ty_name(&self) -> &'static str {
403        Self::TYPE_NAME
404    }
405
406    default fn ty_id(&self) -> FixedId {
407        Self::TYPE_ID
408    }
409
410    default fn ty_version(&self) -> FixedVersion {
411        Self::TYPE_VERSION
412    }
413}
414
415#[cfg(test)]
416mod tests {
417    use crate::name_version_to_hash;
418
419    use super::prelude::*;
420
421    #[test]
422    fn unique_id_typeid_equal_to() {
423        pub struct A1;
424        pub struct A2;
425        fixed_type_id! {
426          #[version((0,1,0))]
427          #[omit_version_hash]
428          A1;
429        }
430        fixed_type_id! {
431          #[version((0,2,0))]
432          #[equal_to(A1)]
433          A2;
434        }
435        assert_eq!(
436            <A1 as FixedTypeId>::TYPE_ID,
437            FixedId::from_type_name(<A1 as FixedTypeId>::TYPE_NAME, None)
438        );
439        assert_eq!(<A1 as FixedTypeId>::TYPE_NAME, "A1");
440        assert_eq!(<A2 as FixedTypeId>::TYPE_NAME, "A2");
441        assert_eq!(<A1 as FixedTypeId>::TYPE_ID, <A2 as FixedTypeId>::TYPE_ID);
442        assert_eq!(
443            <A1 as FixedTypeId>::TYPE_VERSION,
444            FixedVersion::new(0, 1, 0)
445        );
446        assert_eq!(
447            <A2 as FixedTypeId>::TYPE_VERSION,
448            FixedVersion::new(0, 2, 0)
449        );
450    }
451
452    #[test]
453    fn unique_id_generic_ne() {
454        pub struct A<T> {
455            pub _t: T,
456        }
457        fixed_type_id! {
458          A<u8>;
459          A<u16>;
460        }
461        assert_eq!(<A<u8> as FixedTypeId>::TYPE_NAME, "A<u8>");
462        assert_eq!(<A<u16> as FixedTypeId>::TYPE_NAME, "A<u16>");
463        assert_eq!(
464            <A<u8> as FixedTypeId>::TYPE_ID,
465            FixedId::from_type_name(
466                <A<u8> as FixedTypeId>::TYPE_NAME,
467                Some(FixedVersion::new(0, 0, 0))
468            )
469        );
470        assert_eq!(
471            <A<u16> as FixedTypeId>::TYPE_ID,
472            FixedId::from_type_name(
473                <A<u16> as FixedTypeId>::TYPE_NAME,
474                Some(FixedVersion::new(0, 0, 0))
475            )
476        );
477        assert_eq!(
478            <A<u8> as FixedTypeId>::TYPE_VERSION,
479            <A<u16> as FixedTypeId>::TYPE_VERSION
480        );
481        assert_eq!(
482            <A<u8> as FixedTypeId>::TYPE_VERSION,
483            FixedVersion::new(0, 0, 0)
484        );
485        assert_eq!(
486            <A<u16> as FixedTypeId>::TYPE_VERSION,
487            FixedVersion::new(0, 0, 0)
488        );
489    }
490
491    #[test]
492    fn macro_manual_diff() {
493        // with versin hash, default implementation
494        mod a {
495            use super::fixed_type_id;
496            use super::{FixedId, FixedTypeId, FixedVersion};
497
498            pub struct A;
499            fixed_type_id! {
500                A;
501            }
502        }
503        mod b {
504            use super::FixedTypeId;
505            pub struct A;
506            impl FixedTypeId for A {
507                const TYPE_NAME: &'static str = "A";
508            }
509        }
510        assert_eq!(<b::A as FixedTypeId>::TYPE_ID.0, {
511            name_version_to_hash("A", &(0, 0, 0).into())
512        });
513        assert_eq!(
514            <b::A as FixedTypeId>::TYPE_ID.0,
515            <a::A as FixedTypeId>::TYPE_ID.0
516        );
517        assert_eq!(
518            <a::A as FixedTypeId>::TYPE_VERSION,
519            FixedVersion::new(0, 0, 0)
520        );
521        assert_eq!(
522            <b::A as FixedTypeId>::TYPE_VERSION,
523            <a::A as FixedTypeId>::TYPE_VERSION
524        );
525        assert_eq!(<a::A as FixedTypeId>::TYPE_NAME, "A");
526        assert_eq!(
527            <b::A as FixedTypeId>::TYPE_NAME,
528            <a::A as FixedTypeId>::TYPE_NAME
529        );
530    }
531
532    #[cfg(feature = "specialization")]
533    #[test]
534    fn specialization_for_any_type() {
535        pub struct A {
536            pub _t: u8,
537            pub _x: i32,
538        };
539
540        assert_eq!(<A as FixedTypeId>::TYPE_NAME, "NOT_IMPLEMENTED");
541        assert_eq!(
542            <A as FixedTypeId>::TYPE_ID,
543            FixedId::from_type_name("NOT_IMPLEMENTED", Some(FixedVersion::new(0, 0, 0)))
544        );
545        assert_eq!(<A as FixedTypeId>::TYPE_VERSION, FixedVersion::new(0, 0, 0));
546    }
547
548    #[test]
549    fn generic_auto_1_param() {
550        pub struct GenericType<T> {
551            some: T,
552            u32: u32,
553        }
554        use std::ops::Add;
555        pub trait DefTrait {}
556        fixed_type_id! {
557            #[version((0,1,0))]
558            #[omit_version_hash]
559            tests::generic_auto::GenericType<T: FixedTypeId + DefTrait>;
560        };
561        impl DefTrait for u8 {}
562
563        assert_eq!(
564            <GenericType<u8> as FixedTypeId>::TYPE_NAME,
565            "tests::generic_auto::GenericType<u8>"
566        );
567        assert_eq!(
568            <GenericType<u8> as FixedTypeId>::TYPE_VERSION,
569            FixedVersion::new(0, 1, 0)
570        );
571        assert_eq!(
572            <GenericType<u8> as FixedTypeId>::TYPE_ID,
573            FixedId::from_type_name(<GenericType<u8> as FixedTypeId>::TYPE_NAME, None)
574        )
575    }
576
577    #[test]
578    fn generic_auto_2_param() {
579        pub struct GenericType<T, U> {
580            some_t: T,
581            some_u: U,
582            u32: u32,
583        }
584        pub trait DefTrait {}
585        fixed_type_id! {
586            #[version((0,1,0))]
587            #[omit_version_hash]
588            tests::generic_auto::GenericType<T:, U:FixedTypeId + DefTrait>;
589        };
590        impl DefTrait for u8 {}
591
592        assert_eq!(
593            <GenericType<u8, u8> as FixedTypeId>::TYPE_NAME,
594            "tests::generic_auto::GenericType<u8>"
595        );
596        assert_eq!(
597            <GenericType<u8, u8> as FixedTypeId>::TYPE_VERSION,
598            FixedVersion::new(0, 1, 0)
599        );
600        assert_eq!(
601            <GenericType<u8, u8> as FixedTypeId>::TYPE_ID,
602            FixedId::from_type_name(<GenericType<u8, u8> as FixedTypeId>::TYPE_NAME, None)
603        );
604
605        pub struct GenericType2<T, U> {
606            some_t: T,
607            some_u: U,
608            u32: u32,
609        }
610        fixed_type_id! {
611            #[version((0,1,0))]
612            #[omit_version_hash]
613            tests::generic_auto::GenericType2<T:FixedTypeId + DefTrait, U:FixedTypeId + DefTrait>;
614        };
615
616        assert_eq!(
617            <GenericType2<u8, u8> as FixedTypeId>::TYPE_NAME,
618            "tests::generic_auto::GenericType2<u8,u8>"
619        );
620        assert_eq!(
621            <GenericType2<u8, u8> as FixedTypeId>::TYPE_VERSION,
622            FixedVersion::new(0, 1, 0)
623        );
624        assert_eq!(
625            <GenericType2<u8, u8> as FixedTypeId>::TYPE_ID,
626            FixedId::from_type_name(<GenericType2<u8, u8> as FixedTypeId>::TYPE_NAME, None)
627        )
628    }
629
630    #[test]
631    fn generic_auto_equal_to() {
632        pub struct GenericType<T> {
633            some: T,
634            u32: u32,
635        }
636        pub enum EqualType<T> {
637            X(T),
638            Y(u32),
639        }
640        pub trait DefTrait {}
641        fixed_type_id! {
642            tests::generic_auto::EqualType<T: FixedTypeId>;
643        }
644        fixed_type_id! {
645            #[version((0,1,0))]
646            #[omit_version_hash]
647            #[equal_to(EqualType<T>)]
648            tests::generic_auto::GenericType<T: FixedTypeId + DefTrait>;
649        };
650        impl DefTrait for u8 {}
651
652        assert_eq!(
653            <GenericType<u8> as FixedTypeId>::TYPE_NAME,
654            "tests::generic_auto::GenericType<u8>"
655        );
656        assert_eq!(
657            <GenericType<u8> as FixedTypeId>::TYPE_VERSION,
658            FixedVersion::new(0, 1, 0)
659        );
660        assert_eq!(
661            <GenericType<u8> as FixedTypeId>::TYPE_ID,
662            <EqualType<u8> as FixedTypeId>::TYPE_ID
663        )
664    }
665}