├── .gitignore ├── .travis.yml ├── README.md ├── Cargo.toml ├── LICENSE └── src └── lib.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | Cargo.lock 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | cache: cargo 3 | 4 | matrix: 5 | include: 6 | # Required for the `$crate::helper_macro!()` syntax 7 | - rust: 1.30.0 8 | 9 | - rust: stable 10 | 11 | - env: FEATURES="--all-features" 12 | rust: nightly 13 | 14 | script: 15 | - cargo build --verbose $FEATURES 16 | - cargo test --verbose $FEATURES 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # sum_type 2 | 3 | [![Build Status](https://travis-ci.org/Michael-F-Bryan/sum_type.svg?branch=master)](https://travis-ci.org/Michael-F-Bryan/sum_type) 4 | [![license](https://img.shields.io/github/license/michael-f-bryan/sum_type.svg)](https://github.com/Michael-F-Bryan/sum_type/blob/master/LICENSE) 5 | [![Crates.io](https://img.shields.io/crates/v/sum_type.svg)](https://crates.io/crates/sum_type) 6 | [![Docs.rs](https://docs.rs/sum_type/badge.svg)](https://docs.rs/sum_type/) 7 | 8 | A convenience macro for creating a wrapper enum which may be one of several distinct types 9 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "sum_type" 3 | version = "0.1.2-alpha.0" 4 | description = "A convenience macro for creating a wrapper enum which may be one of several distinct types." 5 | authors = ["Michael Bryan "] 6 | repository = "https://github.com/Michael-F-Bryan/sum_type" 7 | categories = ["no-std", "rust-patterns"] 8 | keywords = ["sum", "variant", "enum", "either", "type"] 9 | license = "MIT" 10 | 11 | [badges] 12 | travis-ci = { repository = "Michael-F-Bryan/sum_type" } 13 | 14 | [dependencies] 15 | 16 | [features] 17 | default = [] 18 | generated_example = [] 19 | 20 | [package.metadata.docs.rs] 21 | all-features = true 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Michael Bryan 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! A convenience macro for creating a wrapper enum which may be one of several 2 | //! distinct types. In type theory, this is often referred to as a [sum type]. 3 | //! 4 | //! This crate will work with `no_std` code. 5 | //! 6 | //! # Examples 7 | //! 8 | //! Using the `sum_type!()` macro is rather straightforward. You just define a 9 | //! normal `enum` inside it and the macro will automatically add a bunch of 10 | //! handy trait implementations. 11 | //! 12 | //! For convenience, all attributes are passed through and the macro will 13 | //! derive `From` for each variant. 14 | //! 15 | //! ```rust 16 | //! #[macro_use] 17 | //! extern crate sum_type; 18 | //! 19 | //! sum_type! { 20 | //! #[derive(Debug, Clone, PartialEq)] 21 | //! pub enum MySumType { 22 | //! /// The first variant. 23 | //! First(u32), 24 | //! /// The second variant. 25 | //! Second(String), 26 | //! /// A list of bytes. 27 | //! Third(Vec), 28 | //! } 29 | //! } 30 | //! 31 | //! # fn main() { 32 | //! let first: MySumType = 52.into(); 33 | //! assert_eq!(first, MySumType::First(52)); 34 | //! # } 35 | //! ``` 36 | //! 37 | //! You can also be lazy and omit the variant name. This will name the variant 38 | //! the same thing as its type. 39 | //! 40 | //! ```rust 41 | //! # #[macro_use] 42 | //! # extern crate sum_type; 43 | //! sum_type!{ 44 | //! pub enum Lazy { 45 | //! f32, u32, String, 46 | //! } 47 | //! } 48 | //! # fn main() { 49 | //! let s = Lazy::String("Hello World!".to_string()); 50 | //! # } 51 | //! ``` 52 | //! 53 | //! The [`SumType`] trait is also implemented, allowing a basic level of 54 | //! introspection and dynamic typing. 55 | //! 56 | //! ```rust 57 | //! # #[macro_use] 58 | //! # extern crate sum_type; 59 | //! use sum_type::SumType; 60 | //! # sum_type! { #[derive(Debug, Clone, PartialEq)] pub enum MySumType { 61 | //! # First(u32), Second(String), Third(Vec), } } 62 | //! 63 | //! # fn main() { 64 | //! let first = MySumType::First(52); 65 | //! 66 | //! assert_eq!(first.variant(), "First"); 67 | //! assert_eq!(first.variants(), &["First", "Second", "Third"]); 68 | //! assert!(first.variant_is::()); 69 | //! assert_eq!(first.downcast_ref::(), Some(&52)); 70 | //! # } 71 | //! ``` 72 | //! 73 | //! # Assumptions 74 | //! 75 | //! You need to make sure your type has more than one variant, meaning the 76 | //! following example will fail to compile. 77 | //! 78 | //! ```rust,compile_fail 79 | //! # fn main() {} 80 | //! #[macro_use] 81 | //! extern crate sum_type; 82 | //! 83 | //! sum_type!{ 84 | //! pub enum OneVariant { 85 | //! First(String), 86 | //! } 87 | //! } 88 | //! ``` 89 | //! 90 | //! The `compile_error!()` macro is used to give a (hopefully) useful error 91 | //! message. 92 | //! 93 | //! ```text 94 | //! error: The `OneVariant` type must have more than one variant 95 | //! --> src/lib.rs:37:1 96 | //! | 97 | //! 7 | / sum_type!{ 98 | //! 8 | | pub enum OneVariant { 99 | //! 9 | | First(String), 100 | //! 10 | | } 101 | //! 11 | | } 102 | //! | |_^ 103 | //! | 104 | //! = note: this error originates in a macro outside of the current crate 105 | //! ``` 106 | //! 107 | //! Sum types containing generics, including lifetimes, or which are using 108 | //! visibility modifiers (e.g. `pub(crate)`) aren't (yet!) supported. That 109 | //! means this will fail: 110 | //! 111 | //! ```rust,compile_fail 112 | //! # fn main() {} 113 | //! # #[macro_use] 114 | //! # extern crate sum_type; 115 | //! sum_type!{ 116 | //! TypeWithLifetime<'a> { 117 | //! First(&'a str), 118 | //! Second(usize), 119 | //! } 120 | //! } 121 | //! ``` 122 | //! 123 | //! And so will this: 124 | //! 125 | //! ```rust,compile_fail 126 | //! # fn main() {} 127 | //! # #[macro_use] 128 | //! # extern crate sum_type; 129 | //! sum_type!{ 130 | //! pub(crate) ModifiedVisibility { 131 | //! First(u32), 132 | //! Second(String), 133 | //! } 134 | //! } 135 | //! ``` 136 | //! 137 | //! # Try From 138 | //! 139 | //! `TryFrom` is automatically implemented on your sum type to convert it back to one of its variant types. 140 | //! 141 | //! ```rust 142 | //! #[macro_use] 143 | //! extern crate sum_type; 144 | //! # fn main() { 145 | //! # sum_type! { #[derive(Debug, Clone, PartialEq)] pub enum MySumType { 146 | //! # First(u32), Second(String), Third(Vec), } } 147 | //! use std::convert::TryFrom; 148 | //! 149 | //! let first = MySumType::First(52); 150 | //! 151 | //! let as_u32 = u32::try_from(first); 152 | //! assert_eq!(as_u32, Ok(52)); 153 | //! 154 | //! let second = MySumType::Second(String::from("Not a Vec")); 155 | //! let as_vec_u8 = Vec::::try_from(second); 156 | //! assert!(as_vec_u8.is_err()); 157 | //! 158 | //! let err = as_vec_u8.unwrap_err(); 159 | //! assert_eq!(err.expected_variant, "Third"); 160 | //! assert_eq!(err.actual_variant, "Second"); 161 | //! # } 162 | //! ``` 163 | //! 164 | //! The `generated_example` feature flag will create an example of our 165 | //! `MySumType` which can be viewed using `rustdoc`. 166 | //! 167 | //! [sum type]: https://www.schoolofhaskell.com/school/to-infinity-and-beyond/pick-of-the-week/sum-types 168 | //! [`SumType`]: trait.SumType.html 169 | 170 | #![no_std] 171 | #![deny( 172 | missing_docs, 173 | missing_copy_implementations, 174 | missing_debug_implementations, 175 | unsafe_code 176 | )] 177 | 178 | // re-export so users of our macro have a stable way to import the standard 179 | // library (as `$crate::_core`). 180 | #[doc(hidden)] 181 | pub extern crate core as _core; 182 | 183 | use core::any::Any; 184 | 185 | /// The result of a failed conversion from `TryFrom`. 186 | #[derive(Debug, Copy, Clone, PartialEq)] 187 | pub struct InvalidType { 188 | /// The variant this conversion is valid for. 189 | pub expected_variant: &'static str, 190 | /// The actual variant. 191 | pub actual_variant: &'static str, 192 | /// All possible variants. 193 | pub all_variants: &'static [&'static str], 194 | #[doc(hidden)] 195 | pub __non_exhaustive: (), 196 | } 197 | 198 | /// Various methods for introspection and dynamic typing. 199 | /// 200 | /// # Note 201 | /// 202 | /// This trait is automatically implemented for all types generated by the 203 | /// `sum_type!()` macro. You should never need to implement it manually. 204 | pub trait SumType { 205 | /// The name of the current variant. 206 | fn variant(&self) -> &'static str; 207 | /// A list of all possible variants. 208 | fn variants(&self) -> &'static [&'static str]; 209 | /// Try to get a reference to the inner field if it is a `T`. 210 | fn downcast_ref(&self) -> Option<&T>; 211 | /// Return a mutable reference to the inner field if it is a `T`. 212 | fn downcast_mut(&mut self) -> Option<&mut T>; 213 | /// Is the underlying variant an instance of `T`? 214 | fn variant_is(&self) -> bool; 215 | } 216 | 217 | #[doc(hidden)] 218 | #[macro_export] 219 | macro_rules! __sum_type_try_from { 220 | ($enum_name:ident, $( $name:ident => $variant_type:ty ),*) => { 221 | $( 222 | impl $crate::_core::convert::TryFrom<$enum_name> for $variant_type { 223 | type Error = $crate::InvalidType; 224 | 225 | fn try_from(other: $enum_name) -> Result<$variant_type, Self::Error> { 226 | let variant = $crate::SumType::variant(&other); 227 | let variants = $crate::SumType::variants(&other); 228 | 229 | if let $enum_name::$name(value) = other { 230 | Ok(value) 231 | } else { 232 | Err($crate::InvalidType { 233 | expected_variant: stringify!($name), 234 | actual_variant: variant, 235 | all_variants: variants, 236 | __non_exhaustive: (), 237 | }) 238 | } 239 | } 240 | 241 | } 242 | )* 243 | } 244 | } 245 | 246 | #[doc(hidden)] 247 | #[macro_export] 248 | macro_rules! __sum_type_from { 249 | ($enum_name:ident, $( $name:ident => $variant_type:ty ),*) => { 250 | $( 251 | impl From<$variant_type> for $enum_name { 252 | fn from(other: $variant_type) -> $enum_name { 253 | $enum_name::$name(other) 254 | } 255 | } 256 | )* 257 | } 258 | } 259 | 260 | #[doc(hidden)] 261 | #[macro_export] 262 | macro_rules! __sum_type_trait { 263 | ($enum_name:ident, $( $name:ident => $variant_type:ty ),*) => { 264 | impl $crate::SumType for $enum_name { 265 | fn variants(&self) -> &'static [ &'static str] { 266 | &[ 267 | $( stringify!($name) ),* 268 | ] 269 | } 270 | 271 | fn variant(&self) -> &'static str { 272 | match *self { 273 | $( 274 | $enum_name::$name(_) => stringify!($name), 275 | )* 276 | } 277 | } 278 | 279 | fn downcast_ref(&self) -> Option<&T> { 280 | use $crate::_core::any::Any; 281 | 282 | match *self { 283 | $( 284 | $enum_name::$name(ref value) => (value as &Any).downcast_ref::(), 285 | )* 286 | } 287 | } 288 | 289 | fn downcast_mut(&mut self) -> Option<&mut T> { 290 | use $crate::_core::any::Any; 291 | 292 | match *self { 293 | $( 294 | $enum_name::$name(ref mut value) => (value as &mut Any).downcast_mut::(), 295 | )* 296 | } 297 | } 298 | 299 | fn variant_is(&self) -> bool { 300 | self.downcast_ref::().is_some() 301 | } 302 | } 303 | } 304 | } 305 | 306 | #[doc(hidden)] 307 | #[macro_export] 308 | macro_rules! __assert_multiple_variants { 309 | ($enum_name:ident, $name:ident => $variant_type:ty) => { 310 | compile_error!(concat!( 311 | "The `", 312 | stringify!($enum_name), 313 | "` type must have more than one variant" 314 | )); 315 | }; 316 | ($enum_name:ident, $( $name:ident => $variant_type:ty ),*) => {}; 317 | } 318 | 319 | #[doc(hidden)] 320 | #[macro_export] 321 | macro_rules! __sum_type_impls { 322 | ($enum_name:ident, $( $name:ident => $variant_type:ty ),*) => ( 323 | $crate::__assert_multiple_variants!($enum_name, $( $name => $variant_type ),*); 324 | 325 | $crate::__sum_type_from!($enum_name, $($name => $variant_type),*); 326 | $crate::__sum_type_try_from!($enum_name, $($name => $variant_type),*); 327 | $crate::__sum_type_trait!($enum_name, $($name => $variant_type),*); 328 | ) 329 | } 330 | 331 | /// The entire point. 332 | #[macro_export] 333 | macro_rules! sum_type { 334 | ( 335 | $( #[$outer:meta] )* 336 | pub enum $name:ident { 337 | $( 338 | $( #[$inner:meta] )* 339 | $var_name:ident($var_ty:ty), 340 | )* 341 | }) => { 342 | $( #[$outer] )* 343 | pub enum $name { 344 | $( 345 | $( #[$inner] )* 346 | $var_name($var_ty), 347 | )* 348 | } 349 | 350 | $crate::__sum_type_impls!($name, $( $var_name => $var_ty),*); 351 | }; 352 | ( 353 | $( #[$outer:meta] )* 354 | enum $name:ident { 355 | $( 356 | $( #[$inner:meta] )* 357 | $var_name:ident($var_ty:ty), 358 | )* 359 | }) => { 360 | $( #[$outer] )* 361 | enum $name { 362 | $( 363 | $( #[$inner] )* 364 | $var_name($var_ty), 365 | )* 366 | } 367 | 368 | $crate::__sum_type_impls!($name, $( $var_name => $var_ty),*); 369 | }; 370 | 371 | // "lazy" variations which reuse give the variant the same name as its type. 372 | ( 373 | $( #[$outer:meta] )* 374 | pub enum $name:ident { 375 | $( 376 | $( #[$inner:meta] )* 377 | $var_name:ident, 378 | )* 379 | }) => { 380 | $crate::sum_type!($(#[$outer])* pub enum $name { $( $(#[$inner])* $var_name($var_name), )* }); 381 | }; 382 | ( 383 | $( #[$outer:meta] )* 384 | enum $name:ident { 385 | $( 386 | $( #[$inner:meta] )* 387 | $var_name:ident($var_ty:ty), 388 | )* 389 | }) => { 390 | $crate::sum_type!($(#[$outer])* enum $name { $( $(#[$inner])* $var_name($var_name), )* }); 391 | }; 392 | } 393 | 394 | /// Execute an operation on each enum variant. 395 | /// 396 | /// This macro is short-hand for matching on each variant in an enum and 397 | /// performing the same operation to each. 398 | /// 399 | /// It will expand to roughly the following: 400 | /// 401 | /// ```rust 402 | /// sum_type::sum_type! { 403 | /// #[derive(Debug, PartialEq)] 404 | /// pub enum Foo { 405 | /// First(u32), 406 | /// Second(f64), 407 | /// Third(String), 408 | /// } 409 | /// } 410 | /// 411 | /// let third = Foo::Third(String::from("Hello World")); 412 | /// 413 | /// let got = match third { 414 | /// Foo::First(ref item) => item.to_string(), 415 | /// Foo::Second(ref item) => item.to_string(), 416 | /// Foo::Third(ref item) => item.to_string(), 417 | /// }; 418 | /// ``` 419 | /// 420 | /// # Examples 421 | /// 422 | /// ```rust 423 | /// sum_type::sum_type! { 424 | /// #[derive(Debug, PartialEq)] 425 | /// pub enum Foo { 426 | /// First(u32), 427 | /// Second(f64), 428 | /// Third(String), 429 | /// } 430 | /// } 431 | /// 432 | /// let mut third = Foo::Third(String::from("Hello World")); 433 | /// 434 | /// // Execute some operation on each variant (skipping Second) and get the 435 | /// // return value 436 | /// let mut got = sum_type::defer!(Foo as third; First | Third => |ref item| item.to_string()); 437 | /// 438 | /// assert_eq!(got, "Hello World"); 439 | /// 440 | /// // mutate the variant in place 441 | /// sum_type::defer!(Foo as third; 442 | /// First | Second | Third => |ref mut item| { 443 | /// *item = Default::default(); 444 | /// } 445 | /// ); 446 | /// assert_eq!(third, Foo::Third(String::new())); 447 | /// ``` 448 | /// 449 | /// The `defer!()` macro will panic if it encounters an unhandled variant. 450 | /// 451 | /// ```rust,should_panic 452 | /// sum_type::sum_type! { 453 | /// #[derive(Debug, PartialEq)] 454 | /// pub enum Foo { 455 | /// First(u32), 456 | /// Second(f64), 457 | /// Third(String), 458 | /// } 459 | /// } 460 | /// 461 | /// let mut first = Foo::First(42); 462 | /// 463 | /// sum_type::defer!(Foo as first; Second | Third => |ref _dont_care| ()); 464 | /// ``` 465 | #[macro_export] 466 | macro_rules! defer { 467 | ($kind:ident as $variable:expr; $( $variant:ident )|* => |ref $item:ident| $exec:expr) => { 468 | $crate::defer!(@foreach_variant $kind, $variable; 469 | $( 470 | $kind::$variant(ref $item) => $exec 471 | ),* 472 | ) 473 | }; 474 | ($kind:ident as $variable:expr; $( $variant:ident )|* => |ref mut $item:ident| $exec:expr) => { 475 | $crate::defer!(@foreach_variant $kind, $variable; 476 | $( 477 | $kind::$variant(ref mut $item) => $exec 478 | ),* 479 | ) 480 | }; 481 | (@foreach_variant $kind:ident, $variable:expr; $( $pattern:pat => $exec:expr ),*) => { 482 | match $variable { 483 | $( 484 | $pattern => $exec, 485 | )* 486 | #[allow(unreachable_patterns)] 487 | _ => unreachable!("Unexpected variant, {}, for {}", 488 | <_ as $crate::SumType>::variant(&$variable), 489 | stringify!($kind)), 490 | } 491 | } 492 | } 493 | 494 | /// An example of the generated sum type. 495 | #[cfg(feature = "generated_example")] 496 | #[allow(missing_docs)] 497 | pub mod generated_example { 498 | sum_type! { 499 | #[derive(Debug, Copy, Clone, PartialEq)] 500 | pub enum MySumType { 501 | /// The first variant. 502 | First(u32), 503 | /// The second variant. 504 | Second(&'static str), 505 | /// A list of bytes. 506 | Third(&'static [u8]), 507 | } 508 | } 509 | } 510 | --------------------------------------------------------------------------------