Crate auto_enumerate

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 traits received from #[auto_enum].

Examples

#[auto_enum]’s basic feature is to wrap the value returned by the last if or match expression by an enum that implemented the specified traits.

Generated code

#[auto_enum(Iterator)] // generats an enum with two variants
fn foo(x: i32) -> impl Iterator<Item = i32> {
    match x {
        0 => 1..10,
        _ => vec![5, 10].into_iter(),
    }
}

You can also use #[auto_enum] for expressions and statements.

Generated code

use std::{fs, io, path::Path};

#[auto_enum]
fn output_stream(file: Option<&Path>) -> io::Result<impl io::Write> {
    #[auto_enum(io::Write)]
    let writer = match file {
        Some(f) => fs::File::create(f)?,
        None => io::stdout(),
    };

    Ok(writer)
}

Expression level marker (marker! macro)

#[auto_enum] replaces marker! macros with variants.

Generated code

#[auto_enum(Iterator)] // generats an enum with three variants
fn foo(x: i32) -> impl Iterator<Item = i32> {
    if x < 0 {
        return marker!(x..=0);
    }
    match x {
        0 => 1..10,
        _ => vec![5, 10].into_iter(),
    }
}

Also, if values of two or more are specified by marker! macros, #[auto_enum] can be used for a expression or statement that does not end with a if or match expression.

#[auto_enum(Iterator)]
fn foo(mut x: i32) -> impl Iterator<Item = i32> {
    loop {
        if x < 0 {
            break marker!(x..0);
        } else if x % 5 == 0 {
            break marker!(0..=x);
        }
        x -= 1;
    }
}

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 bar!(x..=0);
    }
    bar!(1..10)
}

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

Blocks and unsafe blocks

Blocks and unsafe blocks are parsed recursively. However, empty blocks are handled in the same as other expressions.

#[auto_enum(Iterator)]
fn foo(x: i32) -> impl Iterator<Item = i32> {
    {
        // The last expression in the block is interpreted as
        // the last expression in the function.
        match x {
            0 => 1..10,
            _ => vec![5, 10].into_iter(),
        }
    }
}

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,
        },
    }
}

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

Supported traits

#[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.

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

[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

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)

  • serde::Serialize - note that it is a different implementation from #[derive(Serialize)].

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.
    • Disable to use no_std instead.
  • 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)

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.

Generated code

There are two steps to generating code.

When using #[auto_enum] for function like the following:

#[auto_enum(Iterator, Clone)]
fn foo(x: i32) -> impl Iterator<Item = i32> + Clone {
    match x {
        0 => 1..10,
        _ => vec![5, 10].into_iter(),
    }
}

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> + Clone {
    #[enum_derive(Iterator, Clone)]
    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. Clone is not directly supported by #[enum_derive], so it passing to #[derive].

Code like this will be generated:

fn foo(x: i32) -> impl Iterator<Item = i32> + Clone {
    #[derive(Clone)]
    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;
        fn next(&mut self) -> ::std::option::Option<Self::Item> {
            match self {
                __Enum1::__T1(x) => x.next(),
                __Enum1::__T2(x) => x.next(),
            }
        }
        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()),
    }
}

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 expressions other than match and if.

Rust Version

The current minimum required Rust version is 1.30.