kiam/
lib.rs

1//! _("kiam" is "when" in Esperanto)_
2//!
3//! This crate introduces [`when!`] macro which provides better syntax for
4//! `if`/`else if`/`else` chains. The syntax is similar to `match`.
5//!
6//! (idea is borrowed from [kotlin][kt-when-expr])
7//!
8//! [kt-when-expr]: https://kotlinlang.org/docs/reference/control-flow.html#when-expression
9#![no_std]
10#![forbid(unsafe_code)]
11#![deny(missing_docs, broken_intra_doc_links)]
12
13/// Better syntax for `if`/`else if`/`else` similar to `match` syntax
14///
15/// ## Usage
16///
17/// Usage is similar to the usage of `match`, but instead of patterns, branches are guarded by boolean expression:
18///
19/// ```rust
20/// kiam::when! {
21///     false => (),
22///     true => (),
23///     // ...
24/// }
25/// ```
26///
27/// `_` can be used as a default branch (it's also required to use `when!` in expression position):
28///
29/// ```rust
30/// let x = kiam::when! {
31///     false => 0,
32///     _ => 1,
33/// };
34///
35/// assert_eq!(x, 1);
36/// ```
37///
38/// You can also use `let <pat> =` to match a pattern, but in difference with `match` you'll have to provide an expression for every pattern:
39///
40/// ```rust
41/// let a = None;
42/// let b = Some(17);
43/// let fallible = || Err(());
44///
45/// let x = kiam::when! {
46///     let Some(x) = a => x,
47///     let Ok(x) = fallible() => x,
48///     let Some(x) = b => (x as u32) + 1,
49///     _ => 1,
50/// };
51///
52/// assert_eq!(x, 18);
53/// ```
54///
55/// Last notes:
56/// - You can also compare structure literals without brackets (you can't do this with `if`/`else if`/`else` chain)
57/// - You can mixup boolean-branches with pattern matching
58/// - Only one branch is executed (not to be confused with `switch` in C-like languages)
59///
60/// ```rust
61/// let mut x = 0;
62///
63/// kiam::when! {
64///     let Ok(_) = Err::<(), _>(()) => x = 1,
65///     true => x = 2,
66///     true => x = 3,
67///     let Some(n) = Some(42) => x = n,
68/// };
69///
70/// assert_eq!(x, 2);
71/// ```
72///
73/// ```compile_fail
74/// #[derive(PartialEq)]
75/// struct Struct { a: i32 }
76///
77/// // This does not compile because of the ambiguity
78/// if Struct { a: 0 } == Struct { a: 0 } {
79///     // ...
80/// }
81/// ```
82///
83/// ```rust
84/// #[derive(PartialEq)]
85/// struct Struct { a: i32 }
86///
87/// kiam::when! {
88///     // This, on the other hand, compiles fine
89///     Struct { a: 0 } == Struct { a: 0 } => {
90///         // ...
91///     },
92/// }
93/// ```
94///
95/// ## Grammar
96///
97/// ```text
98/// grammar:
99///                   ╭───────────────>────────────────╮  ╭────>────╮
100///                   │                                │  │         │
101/// │├──╭── line ──╮──╯── "," ── "_" ── "=>" ── expr ──╰──╯── "," ──╰──┤│
102///     │          │
103///     ╰── "," ───╯
104///
105/// line:
106///     ╭─────────────>─────────────╮
107///     │                           │
108/// │├──╯── "let"/i ── pat ── "=" ──╰── expr ── "=>" ── expr ──┤│
109/// ```
110#[macro_export]
111macro_rules! when {
112    (
113        $(
114            $(let $pat:pat = )? $cond:expr => $branch:expr
115        ),+
116        $(, _ => $def_branch:expr)?
117        $(,)?
118    ) => {
119        $(
120            if $(let $pat = )? $cond {
121                $branch
122            } else
123        )+
124        {
125            $(
126                $def_branch
127            )?
128        }
129    };
130}
131
132#[cfg(test)]
133mod tests {
134    #[test]
135    fn it_works() {
136        let r = when! {
137            false => 0,
138            true => 1,
139            true => 2,
140            _ => 42,
141        };
142
143        assert_eq!(r, 1);
144    }
145
146    #[test]
147    fn pattern() {
148        let r = when! {
149            let Some(x) = None => x,
150            let Some(y) = Some(12) => y + 1,
151            _ => 0,
152        };
153
154        assert_eq!(r, 13);
155    }
156
157    #[test]
158    fn mixed() {
159        let r = when! {
160            false => 0,
161            let Some(_) = None::<bool> => 0,
162            let Some(y) = Some(12) => y + 1,
163            true => 1,
164            _ => 0,
165        };
166
167        assert_eq!(r, 13);
168    }
169
170    #[test]
171    fn r#struct() {
172        #[derive(PartialEq)]
173        struct Struct {
174            x: i32,
175        }
176
177        // won't work with if/elseif/else
178        #[allow(clippy::eq_op)]
179        let r = when! {
180            Struct { x: 0 } == Struct { x: 1 } => 0,
181            Struct { x: 22 } == Struct { x: 22 } => 1,
182            _ => 2,
183        };
184
185        assert_eq!(r, 1);
186    }
187
188    #[test]
189    fn no_def() {
190        let mut x = 0;
191
192        when! {
193            false => x = 18,
194            true => x += 1,
195        }
196
197        assert_eq!(x, 1);
198    }
199}