const_lookup_map/
lib.rs

1#![no_std]
2
3//! Rust map that can be defined in a const context.
4//!
5//! There are two ways to create it:
6//!
7//! ```rust
8//! use const_lookup_map::{ConstLookup, lookup};
9//!
10//! const LOOKUP_MACRO: ConstLookup<3, &str, &str> = lookup! {
11//!     "best" => "better",
12//!     "test" => "testing",
13//!     "guessed" => "guessing",
14//! };
15//! ```
16//!
17//! ```rust
18//! use const_lookup_map::ConstLookup;
19//!
20//! pub const LOOKUP: ConstLookup<4, &str, &str> = ConstLookup::new(
21//!     ["bye", "hallo", "hey", "test"],
22//!     [
23//!         "bye.example.com",
24//!         "hallo.example.com",
25//!         "hey.example.com",
26//!         "test.example.com",
27//!     ],
28//! );
29//! ```
30//!
31//! One note; The keys should be in order/sorted because the get method will use this to effienctly get the value. See [`ConstLookup::check_sorted`]
32//!
33//! # Usage
34//!
35//! ```rust
36//! use const_lookup_map::{ConstLookup, lookup};
37//!
38//! const LOOKUP: ConstLookup<3, &str, &str> = lookup! {
39//!     "best" => "better",
40//!     "test" => "testing",
41//!     "guessed" => "guessing",
42//! };
43//!
44//! fn my_function() {
45//!   assert_eq!(LOOKUP.get(&"best"), Some(&"better"));
46//!   assert_eq!(LOOKUP[&"best"], "better");
47//! }
48//! # my_function()
49//! ```
50
51fn is_sorted<I>(data: I) -> bool
52where
53    I: IntoIterator,
54    I::Item: Ord,
55{
56    let mut it = data.into_iter();
57    match it.next() {
58        None => true,
59        Some(first) => it
60            .scan(first, |state, next| {
61                let cmp = *state <= next;
62                *state = next;
63                Some(cmp)
64            })
65            .all(|b| b),
66    }
67}
68
69#[derive(Debug, PartialEq, Eq)]
70pub struct ConstLookup<const N: usize, K: Ord, V> {
71    pub keys: [K; N],
72    pub values: [V; N],
73}
74
75impl<const N: usize, K: Ord, V> ConstLookup<N, K, V> {
76    /// Returns the number of elements in the map.
77    pub const fn len(&self) -> usize {
78        N
79    }
80
81    pub const fn new(keys: [K; N], values: [V; N]) -> ConstLookup<N, K, V> {
82        ConstLookup { keys, values }
83    }
84
85    /// because keys cannot be checked at compiletime if it is sorted, add this to your tests:
86    ///
87    /// ```rust
88    /// #[test]
89    /// fn verify_my_lookup_is_sorted() {
90    ///     assert!(MY_LOOKUP.check_sorted(), "MY_LOOKUP is not sorted")
91    /// }
92    /// ```
93    pub fn check_sorted(&self) -> bool {
94        is_sorted(&self.keys)
95    }
96
97    /// Returns a reference to the value corresponding to the key.
98    pub fn get(&self, key: &K) -> Option<&V> {
99        let index = self.keys.binary_search(key).ok()?;
100        self.values.get(index)
101    }
102
103    /// Returns true if the map contains a value for the specified key.
104    pub fn contains_key(&self, key: &K) -> bool {
105        self.keys.binary_search(key).is_ok()
106    }
107}
108
109// impl<const N: usize, K: Ord, V> ConstLookup<N, K, V> {
110//     pub const fn const_contains<Q: ~const PartialEq>(&self, key: &K) -> bool {
111//         let mut i = 0;
112//         while i < self.keys.len() {
113//             if key == &self.keys[i] {
114//                 return true;
115//             }
116//             i = i + 1;
117//         }
118
119//         false
120//     }
121// }
122
123impl<const N: usize, K: Ord, V> core::ops::Index<&K> for ConstLookup<N, K, V> {
124    type Output = V;
125
126    fn index(&self, index: &K) -> &V {
127        self.get(index)
128            .expect("key not found in ConstLookup, use `get` for a safe option")
129    }
130}
131
132#[cfg(test)]
133const LOOKUP: ConstLookup<4, &str, &str> = ConstLookup::new(
134    ["bye", "hallo", "hey", "test"],
135    [
136        "bye.example.com",
137        "hallo.example.com",
138        "hey.example.com",
139        "test.example.com",
140    ],
141);
142
143#[macro_export(local_inner_macros)]
144macro_rules! lookup {
145    (@single $($x:tt)*) => (());
146    (@count $($rest:expr),*) => (<[()]>::len(&[$(lookup!(@single $rest)),*]));
147
148    ($($key:expr => $value:expr,)+) => { lookup!($($key => $value),+) };
149    ($($key:expr => $value:expr),*) => {
150        {
151            const _CAP: usize = lookup!(@count $($key),*);
152            let mut keys: [_; _CAP] = unsafe {
153                let arr = core::mem::MaybeUninit::uninit();
154                arr.assume_init()
155            };
156
157            let mut values: [_; _CAP] = unsafe {
158                let arr = core::mem::MaybeUninit::uninit();
159                arr.assume_init()
160            };
161
162            let mut i = 0;
163            $(
164                keys[i] = $key;
165                values[i] = $value;
166                i+=1;
167            )*
168
169            _ = i;
170
171            ConstLookup::new(keys, values)
172        }
173    };
174}
175
176#[cfg(test)]
177const fn large() -> bool {
178    LOOKUP.len() > 100
179}
180
181#[test]
182fn verify_my_lookup_is_sorted() {
183    assert!(LOOKUP.check_sorted(), "LOOKUP is not sorted")
184}
185
186#[test]
187fn get_test() {
188    assert_eq!(Some(&"hey.example.com"), LOOKUP.get(&"hey"));
189}
190
191#[test]
192fn index_test() {
193    assert_eq!("hey.example.com", LOOKUP[&"hey"]);
194}
195
196#[test]
197fn const_func() {
198    assert!(!large())
199}
200
201#[cfg(test)]
202const LOOKUP_MACRO: ConstLookup<3, &str, &str> = lookup! {
203    "best" => "better",
204    "test" => "testing",
205    "guessed" => "guessing",
206};
207
208#[test]
209fn lookup_macro_works_for_const() {
210    assert_eq!(
211        ConstLookup {
212            keys: ["best", "test", "guessed"],
213            values: ["better", "testing", "guessing"]
214        },
215        LOOKUP_MACRO
216    );
217}
218
219#[test]
220fn lookup_macro_works_for_normal_env() {
221    let lookup = lookup! {
222        "best" => "better",
223        "test" => "testing",
224        "guessed" => "guessing",
225    };
226
227    assert_eq!(
228        ConstLookup {
229            keys: ["best", "test", "guessed"],
230            values: ["better", "testing", "guessing"]
231        },
232        lookup
233    );
234}