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}