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)
    };
}