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