as_variant/
lib.rs

1//! Provides a simple macro to convert enums with newtype variants to `Option`s.
2#![no_std]
3
4/// Convert the given enum value to `Option`.
5///
6/// There are two main syntactic forms to this macro:
7///
8/// 1. Variants: Enum expression followed by a comma and then one or more `|`-separated newtype
9///    (one-element tuple) variants paths that should be converted to `Some(_)`. Any other variants
10///    of the enum will be converted to `None`.
11///
12///    ```no_run
13///    # use as_variant::as_variant;
14///    enum Result<T, E> {
15///        Ok(T),
16///        Err(E),
17///    }
18///
19///    impl<T, E> Result<T, E> {
20///        pub fn ok(self) -> Option<T> {
21///            as_variant!(self, Self::Ok)
22///        }
23///    }
24///    ```
25/// 2. Match arm: Enum expression followed by a comma, then a pattern matching one or more variants
26///    of that enum and possibly capturing variables, then a fat right arrow followed by an
27///    expression that will be put inside `Some(_)` if the pattern matches. If the pattern doesn't
28///    match, `None` will be returned.
29///
30///    ```no_run
31///    # use std::{
32///    #     net::{Ipv4Addr, Ipv6Addr},
33///    #     path::PathBuf,
34///    # };
35///    # use as_variant::as_variant;
36///    enum ListenConfig {
37///        Ipv4 { addr: Ipv4Addr, port: u16 },
38///        Ipv6 { addr: Ipv6Addr, port: u16 },
39///        Unix { path: PathBuf },
40///    }
41///
42///    impl ListenConfig {
43///        fn port(&self) -> Option<u16> {
44///            as_variant!(self, Self::Ipv4 { port, .. } | Self::Ipv6 { port, .. } => *port)
45///        }
46///
47///        fn privileged_port(&self) -> Option<u16> {
48///            as_variant!(
49///                self,
50///                // using a guard after the pattern also works:    vvvvvvvvvvvvvvv
51///                Self::Ipv4 { port, .. } | Self::Ipv6 { port, .. } if *port < 1024 => *port,
52///            )
53///        }
54///    }
55///    ```
56///
57/// The enum expression at the start can also be left out, which causes that `as_variant!`
58/// invocation to expand to a closure that does the same thing. That is,
59/// `as_variant!(<variants or match arm>)` is the same as
60/// `|val| as_variant!(val, <variants or match arm>)`. This is especially useful for combinators,
61/// for example [`Option::and_then`] or [`Iterator::filter_map`]:
62///
63/// ```rust
64/// # use std::net::IpAddr;
65/// # use as_variant::as_variant;
66/// let optional_ip_addr = Some("127.0.0.1".parse::<IpAddr>().unwrap());
67/// let optional_ipv4_addr = optional_ip_addr.and_then(as_variant!(IpAddr::V4));
68/// ```
69///
70/// # More Examples
71///
72/// ```no_run
73/// use std::ops::Deref;
74///
75/// use as_variant::as_variant;
76///
77/// enum Value {
78///     Integer(i64),
79///     String(String),
80///     Array(Vec<Value>),
81/// }
82///
83/// impl Value {
84///     pub fn as_integer(&self) -> Option<i64> {
85///         as_variant!(self, Self::Integer).copied()
86///     }
87///
88///     pub fn as_str(&self) -> Option<&str> {
89///         as_variant!(self, Self::String).map(Deref::deref)
90///     }
91///
92///     pub fn as_array(&self) -> Option<&[Value]> {
93///         as_variant!(self, Self::Array).map(Deref::deref)
94///     }
95///
96///     pub fn into_string(self) -> Option<String> {
97///         as_variant!(self, Self::String)
98///     }
99///
100///     pub fn into_array(self) -> Option<Vec<Value>> {
101///         as_variant!(self, Self::Array)
102///     }
103/// }
104/// ```
105///
106/// ```
107/// use as_variant::as_variant;
108///
109/// enum Either3<A, B, C> {
110///     A(A),
111///     B(B),
112///     C(C),
113/// }
114///
115/// impl<T, U> Either3<T, T, U> {
116///     fn as_a_or_b(&self) -> Option<&T> {
117///         as_variant!(self, Self::A | Self::B)
118///     }
119///
120///     fn into_a_or_b(self) -> Option<T> {
121///         as_variant!(self, Self::A | Self::B)
122///     }
123/// }
124///
125/// let a: Either3<_, _, &str> = Either3::A(1);
126/// assert_eq!(a.as_a_or_b(), Some(&1));
127/// assert_eq!(a.into_a_or_b(), Some(1));
128///
129/// let b: Either3<_, _, u8> = Either3::B("hello");
130/// assert_eq!(b.as_a_or_b(), Some(&"hello"));
131/// assert_eq!(b.into_a_or_b(), Some("hello"));
132///
133/// let c: Either3<char, _, _> = Either3::C('c');
134/// assert_eq!(c.as_a_or_b(), None);
135/// assert_eq!(c.into_a_or_b(), None);
136/// ```
137#[macro_export]
138macro_rules! as_variant {
139    ( $enum:expr, $pattern:pat $( if $guard:expr )? => $inner:expr $(,)? ) => {
140        match $enum {
141            $pattern $( if $guard )? => ::core::option::Option::Some($inner),
142            _ => ::core::option::Option::None,
143        }
144    };
145    ( $enum:expr, $( $variants:path )|* ) => {
146        match $enum {
147            $( $variants(inner) )|* => ::core::option::Option::Some(inner),
148            _ => ::core::option::Option::None,
149        }
150    };
151    ( $( $variants:path )|* ) => {
152        |_enum| $crate::as_variant!(_enum, $($variants)|* )
153    };
154    ( $pattern:pat $( if $guard:expr )? => $inner:expr $(,)? ) => {
155        |_enum| $crate::as_variant!(_enum, $pattern $( if $guard )? => $inner)
156    };
157}
158
159#[cfg(test)]
160mod tests {
161    #[test]
162    #[allow(dead_code)]
163    fn smoke_test() {
164        struct A {
165            field: u32,
166        }
167        struct B;
168        struct C;
169
170        enum Alphabet {
171            A(A),
172            B(B),
173            C(C),
174        }
175
176        let letter = Alphabet::A(A { field: 42 });
177
178        let a = as_variant!(&letter, Alphabet::A).unwrap();
179        assert_eq!(a.field, 42);
180
181        let field = as_variant!(&letter, Alphabet::A(A { field }) => field).unwrap();
182        assert_eq!(*field, 42);
183    }
184}