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}