cuid_util/
lib.rs

1//! Common utility functions for CUID generation
2
3// =============================================================================
4// UTILITY FUNCTIONS
5// =============================================================================
6
7// Construcing Base36 Values
8// =========================
9
10/// Converts any number representable as a u128 into a base36 String.
11///
12/// Benchmarking has shown this function to be faster than anything I've been
13/// able to find in a library.
14pub fn to_base_36<N: Into<u128>>(number: N) -> String {
15    const RADIX: u32 = 36;
16    let mut number = number.into();
17
18    // If the number is less than the radix, it can be represented by a single
19    // char. Just push that char and return.
20    if number < RADIX as u128 {
21        return char::from_digit(number as u32, RADIX)
22            // Panic safety: we just checked that `number < RADIX`. RADIX is a
23            // constant (36). `char::from_digit()` only fails if the number is
24            // not a valid digit in the specified radix (and it panics if the
25            // radix is greater than 36). Since we're working with base 36, we
26            // know that 0-35 are valid numbers, and we know that `number` is
27            // from 0-35.
28            .expect("35 and under is always valid")
29            .to_string();
30    }
31
32    // Allocate a string with the appropriate length for the result.
33    //
34    // Number of digits from n in base10 to base36 is log36(n) + 1.
35    //
36    // u128::MAX.log(36).trunc() is ~24, so allocating for 25 chars should always
37    // be enough to avoid reallocation.
38    let mut buffer = String::with_capacity(25);
39
40    while number > 0 {
41        buffer.push(
42            char::from_digit((number % RADIX as u128) as u32, RADIX)
43                // Panic safety: the result of the modulo operation x % y on two
44                // numbers x and y must always be a number in the range [0, y).
45                // `char::from_digit()` here is therefore safe for the same
46                // reasons it is above: we know that any number modulo RADIX
47                // must be a number from 0 through (RADIX - 1). We know that
48                // RADIX is 36 (a constant). And so we know that
49                // `char::from_digit()` will not return an error.
50                .expect("Modulo radix always yields a valid number"),
51        );
52        number /= RADIX as u128;
53    }
54
55    // SAFETY: .as_mut_vec() is unsafe because it allows inserting bytes that
56    // are not valid UTF-8. We are not inserting any bytes, just reversing the
57    // string, so this is safe.
58    unsafe {
59        // We reverse here so that we can push to the back of the string
60        // prior to this, which is faster than pushing to the front of the string
61        buffer.as_mut_vec().reverse();
62    }
63
64    buffer
65}
66
67/// Trait for types that can be converted to base 36.
68pub trait ToBase36 {
69    fn to_base_36(self) -> String;
70}
71
72/// Blanket impl for ToBase36 for anything that can be converted to a u128.
73impl<N> ToBase36 for N
74where
75    N: Into<u128>,
76{
77    fn to_base_36(self) -> String {
78        to_base_36(self)
79    }
80}
81
82#[cfg(test)]
83mod tests {
84    use super::*;
85
86    use proptest::prelude::*;
87
88    proptest! {
89        #[test]
90        fn doesnt_panic(n: u128) {
91            to_base_36(n);
92        }
93
94        #[test]
95        fn expected_output(n: u128) {
96            let val = to_base_36(n);
97            assert_eq!(
98                &format!("{}", radix_fmt::radix_36(n)),
99                &val,
100            );
101            assert_eq!(
102                &num::bigint::BigUint::from_bytes_be(&n.to_be_bytes()).to_str_radix(36),
103                &val
104            )
105        }
106    }
107}