1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
#![doc = include_str!("../Readme.md")]

use std::marker::PhantomData;

use rand::prelude::*;

mod common_traits;
mod conversions;

/// An identifier with a statically associated label type
///
/// Use the `Label` generic to describe what this `Id` is for. If you have an existing struct, you can use that:
///
/// ```
/// # use slid::Id;
/// struct User {
///     name: String,
///     id: Id<User>,
/// }
///
/// // It might be convenient to define a type alias:
/// type UserId = Id<User>;
/// ```
///
/// By default, `Id`s are 16 bytes long. This should provide good collision resistance – i.e. you can assume that 2 randomly generated `Id`s are distinct for all practical purposes. You can customize the `Id` size with the `SIZE` const generic:
///
/// ```
/// # use slid::Id;
/// # struct User;
/// type TinyId<Label> = Id<Label, 4>;
///
/// let _id: TinyId<User> = [0, 0, 0, 42].into();
/// ```
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Id<Label, const SIZE: usize = 16> {
    #[cfg_attr(feature = "serde", serde(with = "serde_arrays"))]
    data: [u8; SIZE],
    _phantom: PhantomData<Label>,
}

// IDs are Send and Sync, regardless of whether their type label is.
// The PhantomData messes with us, so we implement these manually.
//
// [Similar PR by someone smarter than me](https://github.com/rust-lang/rust/pull/68348/files)
unsafe impl<Label, const SIZE: usize> Send for Id<Label, SIZE> {}
unsafe impl<Label, const SIZE: usize> Sync for Id<Label, SIZE> {}

impl<Label, const SIZE: usize> Id<Label, SIZE> {
    /// Generate a new random [`Id`]
    ///
    /// ```
    /// # use slid::Id;
    /// struct User;
    ///
    /// let a = Id::<User>::new_random();
    /// let b = Id::<User>::new_random();
    ///
    /// // For all practical purposes, we can assume that 2 random IDs will be different
    /// // (As long as you use the default ID size or larger)
    /// assert_ne!(a, b);
    /// ```
    pub fn new_random() -> Self {
        let mut rng = rand::thread_rng();
        let mut data = [0; SIZE];
        rng.fill(data.as_mut_slice());

        Self {
            data,
            _phantom: PhantomData,
        }
    }

    /// The bytes representing this [`Id`]
    ///
    /// ```
    /// # use slid::Id;
    /// struct Product;
    ///
    /// let id: Id<Product, 4> = [1,2,3,4].into();
    /// assert_eq!(id.as_bytes(), [1,2,3,4].as_slice());
    /// ```
    pub fn as_bytes(&self) -> &[u8; SIZE] {
        &self.data
    }

    /// Change the label type of this [`Id`]
    ///
    /// ```
    /// # use slid::Id;
    /// struct User;
    /// struct Player;
    ///
    /// let user_id :Id<User>= Id::new_random();
    /// let player_id: Id<Player> = user_id.cast_label();
    /// assert_eq!(user_id.as_bytes(), player_id.as_bytes());
    /// ```
    pub fn cast_label<NewLabel>(self) -> Id<NewLabel, SIZE> {
        Id {
            data: self.data,
            _phantom: PhantomData,
        }
    }
}