[][src]Crate auto_enums

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 and match

    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] // Nightly does not need an empty attribute to the function.
    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 labeled break 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;
        }
    }
  • return (in functions)

    #[auto_enum] can parse the return 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 the return in the scope.

    This analysis is valid only when the following two conditions are satisfied.

    • #[auto_enum] must be used directly for that closure (or the let binding of the closure).
    • ? operator not used in the scope.
    // return (in closures)
    #[auto_enum] // Nightly does not need an empty attribute to the function.
    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)
    }
  • ? operator (in functions)

    #[auto_enum] can parse the ? operator in the scope.

    This analysis is valid only when the return type is Result<T, impl Trait>.

    use std::fmt::{Debug, Display};
    
    // `?` operator (in functions)
    #[auto_enum(Debug, Display)]
    fn func(x: i32) -> Result<i32, impl Debug + Display> {
        if x == 0 {
            Err("`x` is zero")?;
        }
    
        // The last branch of the function is not parsed.
        if x < 0 {
            Err(x)?
        } else {
            Ok(x + 1)
        }
    }

    By default, ? operator is expanded as follows:

    match expr {
        Ok(val) => val,
        Err(err) => return Err(Enum::Veriant(err)),
    }

    When "try_trait" crate feature is enabled, ? operator is expanded as follows (note that this uses an unstable feature):

    match Try::into_result(expr) {
        Ok(val) => val,
        Err(err) => return Try::from_error(Enum::Veriant(err)),
    }
  • ? operator (in closures)

    #[auto_enum] can parse the ? operator in the scope.

    However, #[auto_enum] must be used directly for that closure (or the let binding of the closure).

    use std::fmt::{Debug, Display};
    
    // `?` operator (in closures)
    #[auto_enum] // Nightly does not need an empty attribute to the function.
    fn closure() -> Result<i32, impl Debug + Display> {
        #[auto_enum(Debug, Display)]
        let f = |x| {
            if x == 0 {
                Err("`x` is zero")?
            }
    
            // The last branch of the function is not parsed.
            if x < 0 {
                Err(x)?
            } else {
                Ok(x + 1)
            }
        };
        f(1)
    }
  • Block, unsafe block, method call, parentheses, and type ascription

    The following expressions are recursively searched until an if, match, loop or unsupported expression is found.

    • blocks
    • unsafe blocks
    • method calls
    • parentheses
    • type ascriptions
    // block
    #[auto_enum] // Nightly does not need an empty attribute to the function.
    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] // Nightly does not need an empty attribute to the function.
    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)
    }
    
    // parentheses
    #[auto_enum(Iterator)]
    fn expr_parentheses(x: i32) -> impl Iterator<Item=i32> {
        (if x == 0 { Some(0).into_iter() } else { 0..x })
    }

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.

This example is not tested
// 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.

This example is not tested
// 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

[std|core]::convert

[std|core]::iter

[std|core]::fmt

[std|core]::future

std::io

  • Read (alias: io::Read)
  • BufRead (alias: io::BufRead)
  • Write (alias: io::Write)
  • Seek (alias: io::Seek)

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 from enum<Option<T1>,..> to Option<enum<T1,..>>

    • transpose - convert from enum<Result<T1, E1>,..> to Result<enum<T1,..>, enum<E1,..>>

    • transpose_ok - convert from enum<Result<T1, E>,..> to Option<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 from enum<Result<T, E1>,..> to Result<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 than Debug, Display and Write.
  • 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.
  • try_trait

    • Disabled by default.
    • Make ? operator support more flexible, and to make iterator implementation more effective.
    • This requires Rust Nightly and you need to enable the unstable try_trait feature gate.
  • 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)

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.

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.