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}