Crate auto_enums
source ·Expand description
A library for to allow multiple return types by automatically generated enum.
This library provides the following attribute macros:
-
#[auto_enum]
Parses syntax, creates the enum, inserts variants, and passes specified traits to
#[enum_derive]
. -
#[enum_derive]
Implements specified traits to the enum.
#[auto_enum]
#[auto_enum]
’s most basic feature is to wrap the value returned by the last if or match expression by an enum that implemented the specified traits.
#[auto_enum(Iterator)]
fn foo(x: i32) -> impl Iterator<Item = i32> {
match x {
0 => 1..10,
_ => vec![5, 10].into_iter(),
}
}
#[auto_enum]
generates code in two stages.
First, #[auto_enum]
will do the following.
- parses syntax
- creates the enum
- inserts variants
Code like this will be generated:
fn foo(x: i32) -> impl Iterator<Item = i32> {
#[enum_derive(Iterator)]
enum __Enum1<__T1, __T2> {
__T1(__T1),
__T2(__T2),
}
match x {
0 => __Enum1::__T1(1..10),
_ => __Enum1::__T2(vec![5, 10].into_iter()),
}
}
Next, #[enum_derive]
implements the specified traits.
Code like this will be generated:
fn foo(x: i32) -> impl Iterator<Item = i32> {
enum __Enum1<__T1, __T2> {
__T1(__T1),
__T2(__T2),
}
impl<__T1, __T2> ::std::iter::Iterator for __Enum1<__T1, __T2>
where
__T1: ::std::iter::Iterator,
__T2: ::std::iter::Iterator<Item = <__T1 as ::std::iter::Iterator>::Item>,
{
type Item = <__T1 as ::std::iter::Iterator>::Item;
#[inline]
fn next(&mut self) -> ::std::option::Option<Self::Item> {
match self {
__Enum1::__T1(x) => x.next(),
__Enum1::__T2(x) => x.next(),
}
}
#[inline]
fn size_hint(&self) -> (usize, ::std::option::Option<usize>) {
match self {
__Enum1::__T1(x) => x.size_hint(),
__Enum1::__T2(x) => x.size_hint(),
}
}
}
match x {
0 => __Enum1::__T1(1..10),
_ => __Enum1::__T2(vec![5, 10].into_iter()),
}
}
Positions where #[auto_enum]
can be used.
#[auto_enum]
can be used in the following three places. However, since stmt_expr_attributes and proc_macro_hygiene are not stabilized, you need to use empty #[auto_enum]
for functions except nightly.
-
functions
#[auto_enum(Iterator)] fn func(x: i32) -> impl Iterator<Item=i32> { if x == 0 { Some(0).into_iter() } else { 0..x } }
-
expressions
#[auto_enum] // Nightly does not need an empty attribute to the function. fn expr(x: i32) -> impl Iterator<Item=i32> { #[auto_enum(Iterator)] match x { 0 => Some(0).into_iter(), _ => 0..x, } }
-
let binding
#[auto_enum] // Nightly does not need an empty attribute to the function. fn let_binding(x: i32) -> impl Iterator<Item=i32> { #[auto_enum(Iterator)] let iter = match x { 0 => Some(0).into_iter(), _ => 0..x, }; iter }
Supported syntax
-
if
andmatch
Wrap each branch with a variant.
// if #[auto_enum(Iterator)] fn expr_if(x: i32) -> impl Iterator<Item=i32> { if x == 0 { Some(0).into_iter() } else { 0..x } } // match #[auto_enum] fn expr_match(x: i32) -> impl Iterator<Item=i32> { #[auto_enum(Iterator)] let iter = match x { 0 => Some(0).into_iter(), _ => 0..x, }; iter }
-
loop
Wrap each
break
with a variant. Nested loops and labeledbreak
are also supported.#[auto_enum(Iterator)] fn expr_loop(mut x: i32) -> impl Iterator<Item = i32> { loop { if x < 0 { break x..0; } else if x % 5 == 0 { break 0..=x; } x -= 1; } }
-
Block, unsafe block and method call
Blocks, unsafe blocks, and method calls are recursively searched until an
if
,match
,loop
or unsupported expression is found.// block #[auto_enum] fn expr_block(x: i32) -> impl Iterator<Item=i32> { #[auto_enum(Iterator)] { if x == 0 { Some(0).into_iter() } else { 0..x } } } // method call #[auto_enum] fn expr_method(x: i32) -> impl Iterator<Item=i32> { #[auto_enum(Iterator)] match x { 0 => Some(0).into_iter(), _ => 0..x, }.map(|y| y + 1) }
-
return
(in functions)#[auto_enum]
can parse thereturn
in the scope.This analysis is valid only when the return type is
impl Trait
.// return (in functions) #[auto_enum(Iterator)] fn func(x: i32) -> impl Iterator<Item=i32> { if x == 0 { return Some(0).into_iter(); } if x > 0 { 0..x } else { x..=0 } }
-
return
(in closures)#[auto_enum]
can parse thereturn
in the scope.However,
#[auto_enum]
must be used directly for that closure (or the let binding of the closure).// return (in closures) #[auto_enum] fn closure() -> impl Iterator<Item=i32> { #[auto_enum(Iterator)] let f = |x| { if x == 0 { return Some(0).into_iter(); } if x > 0 { 0..x } else { x..=0 } }; f(1) }
Parse nested branches
You can parse nested branches by #[rec]
attribute.
#[auto_enum(Iterator)]
fn foo(x: i32) -> impl Iterator<Item = i32> {
match x {
0 => 1..10,
#[rec]
_ => match x {
1 => vec![5, 10].into_iter(),
_ => 0..=x,
},
}
}
Expression that no value will be returned
If the last expression of a branch is one of the following, it is interpreted that no value will be returned (variant assignment is skipped).
panic!(..)
unreachable!(..)
return
break
continue
None?
Err(..)?
- Expression level marker (
marker!
macro). - An item definition.
#[auto_enum(Iterator)]
fn foo(x: i32) -> impl Iterator<Item = i32> {
match x {
0 => 1..10,
1 => panic!(), // variant assignment is skipped
_ => vec![5, 10].into_iter(),
}
}
You can also skip that branch explicitly by #[never]
attribute.
#[auto_enum(Iterator)]
fn foo(x: i32) -> impl Iterator<Item = i32> {
match x {
0 => 1..10,
#[never]
1 => loop {
panic!()
},
_ => vec![5, 10].into_iter(),
}
}
You can also skip all branches by never
option.
#[auto_enum(never, Iterator)]
fn foo(x: i32) -> impl Iterator<Item = i32> {
match x {
0 => loop {
return marker!(1..10);
},
1 => loop {
panic!()
},
_ => loop {
return marker!(vec![5, 10].into_iter());
},
}
}
Expression level marker (marker!
macro)
#[auto_enum]
replaces marker!
macros with variants.
If values of two or more are specified by marker!
macros, #[auto_enum]
can be used for unsupported expressions and statements.
#[auto_enum(Iterator)]
fn foo(x: i32) -> impl Iterator<Item = i32> {
if x < 0 {
return x..=0;
}
marker!(1..10)
}
The default name of the macro is "marker"
, but you can change it by marker
option.
#[auto_enum(marker(bar), Iterator)]
fn foo(x: i32) -> impl Iterator<Item = i32> {
if x < 0 {
return x..=0;
}
bar!(1..10)
}
Rust Nightly
When using #[auto_enum]
for expressions and statements, #[auto_enum]
for function is unnecessary.
// Add this to your crate root:
#![feature(proc_macro_hygiene, stmt_expr_attributes)]
fn foo(x: i32) -> i32 {
#[auto_enum(Iterator)]
let iter = match x {
0 => 1..10,
_ => vec![5, 10].into_iter(),
};
iter.fold(0, |sum, x| sum + x)
}
You can also return closures.
// Add this to your crate root:
#![feature(fn_traits, unboxed_closures)]
#[auto_enum(Fn)]
fn foo(x: bool) -> impl Fn(i32) -> i32 {
if x {
|y| y + 1
} else {
|z| z - 1
}
}
#[enum_derive]
#[enum_derive]
implements the supported traits and passes unsupported traits to #[derive]
.
If you want to use traits that are not supported by #[enum_derive]
, you can use another crate that provides proc_macro_derive
, or you can define proc_macro_derive
yourself(derive_utils probably can help it).
Basic usage of #[enum_derive]
// `#[enum_derive]` implements `Iterator`, and `#[derive]` implements `Clone`.
#[enum_derive(Iterator, Clone)]
enum Foo<A, B> {
A(A),
B(B),
}
#[enum_derive]
adds the dependency of the specified trait if it is not specified.
// `#[enum_derive]` implements `Iterator` and `ExactSizeIterator`.
#[enum_derive(ExactSizeIterator)]
enum Foo<A, B> {
A(A),
B(B),
}
Supported traits
[std|core] libraries
Note that some traits have aliases.
[std|core]::ops
Deref
DerefMut
Index
IndexMut
Fn
(nightly-only)FnMut
(nightly-only)FnOnce
(nightly-only)RangeBounds
[std|core]::convert
[std|core]::iter
Iterator
DoubleEndedIterator
ExactSizeIterator
FusedIterator
TrustedLen
(nightly-only)Extend
[std|core]::fmt
Debug
(alias:fmt::Debug
) - generated codeDisplay
(alias:fmt::Display
)fmt::Binary
fmt::LowerExp
fmt::LowerHex
fmt::Octal
fmt::Pointer
fmt::UpperExp
fmt::UpperHex
fmt::Write
[std|core]::future
Future
- nightly-only
std::io
std::error
External libraries
You can add support for external library by activating the each crate feature.
futures(v0.3)
(requires "futures"
crate feature)
futures(v0.1)
(requires "futures01"
crate feature)
quote
(requires "proc_macro"
crate feature)
rayon
(requires "rayon"
crate feature)
serde
(requires "serde"
crate feature)
Static methods
These don’t derive traits, but derive static methods instead.
-
Transpose
(requires"transpose_methods"
crate feature) - this derives the following conversion methods.-
transpose
- convert fromenum<Option<T1>,..>
toOption<enum<T1,..>>
-
transpose
- convert fromenum<Result<T1, E1>,..>
toResult<enum<T1,..>, enum<E1,..>>
-
transpose_ok
- convert fromenum<Result<T1, E>,..>
toOption<enum<T1,..>, E>
Examples:
use std::{fs, io, path::Path}; #[auto_enum(Transpose, Write)] fn output_stream(file: Option<&Path>) -> io::Result<impl io::Write> { match file { Some(f) => fs::File::create(f), None => Ok(io::stdout()), }.transpose_ok() }
-
transpose_err
- convert fromenum<Result<T, E1>,..>
toResult<T, enum<E1,..>>
-
Crate Features
-
std
- Enabled by default.
- Generate code for
std
library. - Disable this feature to generate code for
no_std
.
-
fmt
- Disabled by default.
- Use
[std|core]::fmt
’s traits other thanDebug
,Display
andWrite
.
-
type_analysis
-
Disabled by default.
-
Analyze return type of function and
let
binding.Examples:
#[auto_enum] // there is no need to specify std library's traits fn foo(x: i32) -> impl Iterator<Item = i32> { match x { 0 => 1..10, _ => vec![5, 10].into_iter(), } }
Please be careful if you return another traits with the same name.
-
-
transpose_methods
- Disabled by default.
- Use
transpose*
methods.
-
unstable
- Disabled by default.
- Use unstable features to make attribute macros more effective.
- The traits supported by
#[enum_derive]
are not related to this feature. - This requires Rust Nightly.
Using external libraries (disabled by default)
-
futures
- futures(v0.3) -
futures01
- futures(v0.1) -
proc_macro
- quote -
rayon
- rayon -
serde
- serde
Enable unstable features of [std|core] libraries (disabled by default, nightly-only)
For these features, you need to enable the unstable feature gate of the same name.
-
exact_size_is_empty
- ImplementsExactSizeIterator::is_empty
. -
read_initializer
- Implementsio::Read::read_initializer
. -
try_trait
- Make iterator implementation more effective. -
unsized_locals
- AllowIndex<Idx: ?Sized>
andIndexMut<Idx: ?Sized>
.
Known limitations
-
There needs to explicitly specify the trait to be implemented (
type_analysis
crate feature reduces this limitation). -
There needs to be marker macros for unsupported expressions.
Rust Version
The current minimum required Rust version is 1.30.