choose_from/
lib.rs

1//! Choose a K-selection of values from N choices, where N and K are set at compile time (or not).
2//!
3//! # Why is this useful?
4//!
5//! One use case (the one that made me write this), would be to ensure a function provided by a library user
6//! only returns a selection of values provided to it.
7//!
8//! For example:
9//! ```
10//! #[derive(Clone, Copy)]
11//! enum Suit {
12//!     Clubs,
13//!     Diamonds,
14//!     Hearts,
15//!     Spades,
16//! }
17//!
18//! #[derive(Clone, Copy)]
19//! struct Suits<const N: usize>([Suit; N]);
20//!
21//! impl<const N: usize> Suits<N> {
22//!     // constructor
23//!     pub fn with_suits(suits: [Suit; N]) -> Suits<N> {
24//!         Suits(suits)
25//!     }
26//!     
27//!     // where chooser is some external function that chooses from the provided suits
28//!     pub fn choose_suit<C>(&self, chooser: C)
29//!     where
30//!         C: FnOnce([Suit; N]) -> Suit
31//!     {
32//!         // have user choose some suit
33//!         let suit = chooser(self.0);
34//!
35//!         // do stuff with suit
36//!         // ...
37//!     }
38//! }
39//! ```
40//! In the above case, we have a container that holds suits, and we
41//! want the user to choose one suit from our inner suit array. As the
42//! function is currently written however, the user could return any arbitrary
43//! suit, even if it was not contained within our array.
44//! ```
45//! # #[derive(Clone, Copy)]
46//! # enum Suit {
47//! #     Clubs,
48//! #     Diamonds,
49//! #     Hearts,
50//! #     Spades,
51//! # }
52//! #
53//! # #[derive(Clone, Copy)]
54//! # struct Suits<const N: usize>([Suit; N]);
55//! #
56//! # impl<const N: usize> Suits<N> {
57//! #     // constructor
58//! #     pub fn with_suits(suits: [Suit; N]) -> Suits<N> {
59//! #         Suits(suits)
60//! #     }
61//! #     pub fn choose_suit<C>(&self, chooser: C)
62//! #     where
63//! #         C: FnOnce([Suit; N]) -> Suit
64//! #     {
65//! #         // have user choose some suit
66//! #         let suit = chooser(self.0);
67//! #
68//! #         // do stuff with suit
69//! #         // ...
70//! #     }
71//! # }
72//! let suits = Suits::with_suits([Suit::Clubs, Suit::Diamonds]);
73//!     
74//! // this means choose_suit will get a spades, even though our array does not
75//! // include spades
76//! suits.choose_suit(|_| Suit::Spades);
77//! ```
78//! This is where we can use the functions in this library to force the user to take one of our
79//! provided choices
80//! ```
81//! # #[derive(Clone, Copy)]
82//! # enum Suit {
83//! #     Clubs,
84//! #     Diamonds,
85//! #     Hearts,
86//! #     Spades,
87//! # }
88//! #
89//! # #[derive(Clone, Copy)]
90//! # struct Suits<const N: usize>([Suit; N]);
91//! #
92//! use choose_from::{select_from_fixed, Choice};
93//!
94//! impl<const N: usize> Suits<N> {
95//! #   pub fn with_suits(suits: [Suit; N]) -> Suits<N> {
96//! #         Suits(suits)
97//! #   }
98//!     // ...
99//!     // where chooser is some external function that chooses from the provided suits
100//!     pub fn choose_suit<C>(&self, chooser: C)
101//!     where
102//!         C: FnOnce([Choice<'_, Suit>; N]) -> [Choice<'_, Suit>; 1]
103//!     {
104//!         // have user choose some suit (this suit is guaranteed to be from our choices)
105//!         let [suit]: [Suit; 1] = select_from_fixed(self.0).with(chooser);
106//!
107//!         // do stuff with suit
108//!         // ...
109//!     }
110//!     // ...
111//! }
112//! ```
113//! ## Alternative?
114//!
115//! If you thought about it for a bit, you may realize that you can just use an enum
116//! over "choosable" values, and then provide a mapping from that enum to our original
117//! values:
118//! ```
119//! # #[derive(Clone, Copy)]
120//! # enum Suit {
121//! #     Clubs,
122//! #     Diamonds,
123//! #     Hearts,
124//! #     Spades,
125//! # }
126//! #
127//! # #[derive(Clone, Copy)]
128//! # struct Suits<const N: usize>([Suit; N]);
129//! #
130//! pub enum ChoosableSuit {
131//!     Clubs,
132//!     Diamonds,
133//! }
134//!
135//! impl ChoosableSuit {
136//!     pub fn to_suit(self) -> Suit {
137//!         match self {
138//!             ChoosableSuit::Clubs => Suit::Clubs,
139//!             ChoosableSuit::Diamonds => Suit::Diamonds,
140//!         }
141//!     }
142//! }
143//!
144//! impl<const N: usize> Suits<N> {
145//! #   pub fn with_suits(suits: [Suit; N]) -> Suits<N> {
146//! #         Suits(suits)
147//! #   }
148//!     // ...
149//!     // where chooser is some external function that chooses from the provided suits
150//!     pub fn choose_suit<C>(&self, chooser: C)
151//!     where
152//!         C: FnOnce([ChoosableSuit; 2]) -> ChoosableSuit
153//!     {
154//!         // have user choose some suit (let's imagine these ChoosableSuits are from our choices)
155//!         let suit: Suit = chooser([ChoosableSuit::Clubs, ChoosableSuit::Diamonds]).to_suit();
156//!
157//!         // do stuff with suit
158//!         // ...
159//!     }
160//!     // ...
161//! }
162//! ```
163//! This works! But this only works for returning a single value from a subset known at compile time (plus it is kind of annoying
164//! to write a bunch of boilerplate enums everytime you want to choose between some values).
165//!
166//! When we try to return multiple values (as an array, tuple, Vec, etc.), we run into a similar problem:
167//! we can't stop a user from providing two or more duplicate choices (this is an example of choices *with* replacement, when
168//! we want choices *without* replacement).
169//!
170//! ## Concrete use case
171//!
172//! Let's imagine `chooser` to be some GUI selector. This allows us to abstract away the
173//! logic of actually getting a choice from an application user to the user of our library.
174//! Which means that multiple implementations of `chooser` can use our library (web app, CLI, desktop, etc.).
175//!
176//! # How do they work?
177//!
178//! Values are assured to be from the selection through two ways.
179//! First the only constructor for [Choice] is private
180//! ```compile_fail
181//! use choose_from::select_from_fixed;
182//!
183//! // we cannot access the private constructor. And it requires a reference
184//! // to a Guard that we cannot construct
185//! let one = Choice::with_guard(1, unreachable!());
186//! ```
187//! So we know choices cannot be created out of thin air (only within this library), but what about the
188//! owned [Choice]s provided to us through [`with`](crate::Selector::with) (or similar methods)?
189//! If we moved them out of the closure (since we have ownership), and then used them as choices
190//! for a new [select_from] with the same type, then we could return values that aren't from the
191//! available choices! If we try to do that:
192//! ```compile_fail
193//! use choose_from::select_from_fixed;
194//!
195//! let mut smuggler = Vec::new();
196//! select_from(vec![1, 2, 3, 4]).any_with(|choices| {
197//!     // try to move last three values out of the closure
198//!     smuggle.extend(choices.drain(1..));
199//!     choices
200//! });
201//!
202//! // use the smuggled value later to do nefarious stuff
203//! // if this was possible weird_values wouldn't be from our
204//! // provided choices
205//! let weird_values = select_from(vec![]).any_with(|_| smuggler);
206//! ```
207//! This fails to compile. Remember the Guard we mentioned earlier? All choices have a
208//! lifetime specifier. They don't actually hold any value, but they act as if they hold
209//! a reference to a Guard. This stops a [Choice] from living longer than the call to
210//! [with](crate::SelectorFixed) (and similar methods), since the reference for each Guard
211//! only lives as long as the body of the method (since [Choice] "holds" a reference to the guard,
212//! it cannot live longer than it). Both of these steps combine to ensure that the `chooser`
213//! function *MUST* select value(s) from the provided ones.
214//!
215//! If you are interested in learning more try reading the code, it is quite simple.
216
217mod choice;
218pub mod fixed;
219pub mod selector;
220
221pub use choice::Choice;
222use choice::Guard;
223use fixed::SelectorFixed;
224use selector::Selector;
225
226/// Wraps our arbitrary number of choices and allows us to force a function/closure to
227/// choose from them
228/// ```
229/// use choose_from::select_from;
230///
231/// // every other number from 1 to 99 starting at 1
232/// let choices: Vec<i32> = (1..100).step_by(2).collect();
233///
234/// let chosen: [i32; 4] = select_from(choices).with(|mut choices| {
235///     // take first four as our selection
236///     choices
237///         .drain(..4)
238///         .collect::<Vec<_>>()
239///         .try_into()
240///         .unwrap()
241/// });
242///
243/// assert_eq!(chosen, [1, 3, 5, 7]);
244/// ```
245pub fn select_from<I, T>(choices: I) -> Selector<I, T>
246where
247    I: IntoIterator<Item = T>,
248{
249    Selector::with_choices(choices)
250}
251
252/// Wraps our fixed number of choices and allows us to force a function/closure to choose
253/// from them
254/// ```
255/// use choose_from::select_from_fixed;
256/// let chosen = select_from_fixed(["Hi", "how", "are ya?"]).with(|[first, second, third]| {
257///     // the provided choices allow inspection of the values
258///     assert_eq!(*first, "Hi");
259///     
260///     // this is our selection
261///     [first, third]
262/// });
263///
264/// assert_eq!(chosen, ["Hi", "are ya?"]);
265/// ```
266pub fn select_from_fixed<const N: usize, T>(choices: [T; N]) -> SelectorFixed<N, T> {
267    SelectorFixed::with_choices(choices)
268}
269
270#[cfg(test)]
271mod tests {
272    use super::*;
273
274    macro_rules! test_fixed_choices {
275        ($func_name:ident, $choices: expr, $chosen_type: ty, $func: expr, $result: expr) => {
276            #[test]
277            fn $func_name() {
278                let choices = $choices;
279                let chosen: $chosen_type = select_from_fixed(choices).with($func);
280
281                assert_eq!(chosen, $result);
282            }
283        };
284    }
285
286    test_fixed_choices!(
287        choose_two_i32s_from_fixed_four,
288        [1, 2, 3, 4],
289        [i32; 2],
290        |[one, two, _, _]| { [one, two] },
291        [1, 2]
292    );
293
294    test_fixed_choices!(
295        choose_two_strs_from_fixed_three,
296        ["a", "b", "c"],
297        [&str; 2],
298        |[_, b, c]| { [b, c] },
299        ["b", "c"]
300    );
301
302    // TODO: write more tests
303}