triblespace-core 0.33.0

The triblespace core implementation.
Documentation
use rand::thread_rng;
use rand::RngCore;
use std::cell::RefCell;

use super::ExclusiveId;
use super::Id;

/// Generator for Fast Unsafe Compressible IDs (FUCIDs).
///
/// Each source holds a random salt and an incrementing counter. Sequential ids
/// from the same source differ by only a few bits, enabling efficient
/// compression while remaining globally unique with high probability.
pub struct FUCIDsource {
    salt: u128,
    counter: u128,
}

impl Default for FUCIDsource {
    /// Creates a new [`FUCIDsource`] with a random salt (equivalent to [`FUCIDsource::new`]).
    fn default() -> Self {
        Self::new()
    }
}

impl FUCIDsource {
    /// Creates a new source with a randomly chosen salt.
    pub fn new() -> Self {
        Self {
            salt: {
                let mut rng = thread_rng();
                let mut rand_bytes = [0; 16];
                rng.fill_bytes(&mut rand_bytes[..]);

                u128::from_be_bytes(rand_bytes)
            },
            counter: 0,
        }
    }

    /// Returns the next unique [`ExclusiveId`] from this source.
    pub fn mint(&mut self) -> ExclusiveId {
        let next_id = self.counter ^ self.salt;
        self.counter += 1;
        let id = next_id.to_be_bytes();
        ExclusiveId::force(
            Id::new(id).expect("The probability for counter ^ salt = 0 should be neglegible."),
        )
    }
}

thread_local!(static GEN_STATE: RefCell<FUCIDsource> = RefCell::new(FUCIDsource::new()));

/// # Fast Unsafe Compressible IDs (FUCIDs)
///
/// FUCIDs are 128-bit identifiers generated by XORing a random `salt`
/// with an incrementing counter. Each source (e.g., each thread) produces a unique
/// sequence of identifiers with high global and low local entropy.  
/// This ensures global uniqueness while allowing for efficient compression and high data locality.
///
/// ## Collision Resistance
///
/// - Within a single source, the exhaustive traversal of the 128-bit space
///   minimizes the risk of collisions.
/// - Across different sources, the uniqueness of the `salt` ensures global uniqueness
///   without requiring coordination.
///
/// ## Considerations
///
/// - Due to low entropy between sequential IDs from the same source,
///   FUCIDs may be more vulnerable to hardware errors (e.g., bit-flips).  
/// - Despite having some local randomness, FUCIDs are relatively easy to predict
///   or guess on a per-source basis due to their low local entropy.
///
/// ## Usage Examples
///
/// Each thread has a thread-local `FUCIDsource` that is automatically used when
/// generating IDs via the `fucid` function. This approach is simple and efficient
/// for most use cases.
///
/// ```rust
/// use triblespace_core::id::fucid;
///
/// let id1 = fucid();
/// let id2 = fucid();
/// assert_ne!(id1, id2);
/// ```
///
/// For scenarios where more explicit control is desired, such as creating multiple
/// independent sequences or avoiding thread-local storage, a dedicated `FUCIDsource`
/// can be instantiated and used directly.
///
/// ```rust
/// use triblespace_core::id::FUCIDsource;
///
/// let mut source = FUCIDsource::new();
/// let id1 = source.mint();
/// let id2 = source.mint();
/// assert_ne!(id1, id2);
/// ```
///
/// Note that creating a new `FUCIDsource` for each ID is equivalent to the
/// [RNGID](crate::id::rngid::rngid) scheme.
pub fn fucid() -> ExclusiveId {
    GEN_STATE.with_borrow_mut(|gen| gen.mint())
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn unique() {
        assert!(fucid() != fucid());
    }
}