#diff #enum #macro #accessors

macro diff-enum

A macro library to help defining enum variants by their differences

5 releases

0.1.4 Mar 28, 2019
0.1.3 Feb 6, 2019
0.1.2 Feb 1, 2019
0.1.1 Jan 31, 2019
0.1.0 Jan 30, 2019

#1933 in Procedural macros

30 downloads per month
Used in detect_git_service

MIT license

10KB
96 lines

#[diff_enum] macro for Rust

crates.io documentation CI Status

This is a small Rust library provides one attribute macro #[diff_enum::common_fields] to help defining enum variants by their differences. It is useful when you need to handle data which are almost the same, but different partially.

By the attribute macro, common fields among all variants and different fields for each variant can be defined separately. Common fields are defined once. Additionally accessor methods for common fields are automatically defined.

For example,

extern crate diff_enum;
use diff_enum::common_fields;

#[common_fields {
    user: String,
    name: String,
    stars: u32,
    issues: u32,
}]
#[derive(Debug)]
enum RemoteRepo {
    GitHub {
        language: String,
        pull_requests: u32,
    }
    GitLab {
        merge_requests: u32,
    }
}

is expanded to

#[derive(Debug)]
enum RemoteRepo {
    GitHub {
        language: String,
        pull_requests: u32,
        user: String,
        name: String,
        stars: u32,
        issues: u32,
    }
    GitLab {
        merge_requests: u32,
        user: String,
        name: String,
        stars: u32,
        issues: u32,
    }
}

Additionally, accessor functions are defined for common fields. For example,

let repo = RemoteRepo::GitHub {
    user: "rust-lang".to_string(),
    name: "rust".to_string(),
    language: "rust".to_string(),
    issues: 4536,
    pull_requests: 129,
    stars: 33679,
};

println!("User: {}", repo.user());

Alternative

Without this crate, it's typical to separate the data into a struct with common fields and a enum variants for differences.

For above RemoteRepo example,

enum RemoteRepoKind {
    GitHub {
        language: String,
        pull_requests: u32,
    }
    GitLab {
        merge_requests: u32,
    }
}
struct RemoteRepo {
    user: String,
    name: String,
    stars: u32,
    issues: u32,
    kind: RemoteRepoKind,
}

This solution has problems as follows:

  • Fields are split into 2 parts for the reason of Rust enum. Essentially number of issues and number of pull requests are both properties of a GitHub repository. As natural data structure they should be in the same flat struct.
  • Naming the inner enum is difficult. Here I used 'Kind' to separate parts. But is it appropriate? 'Kind' is too generic name with weak meaning. The weak name comes from awkwardness of the data structure.

Installation

Please add this crate to dependencies in your project's Cargo.toml.

[dependencies]
diff-enum = "0.1"

Usage

At first, please load the crate.

extern crate diff_enum;
use diff_enum::common_fields;

And use #[common_fields] attribute macro for your enum definitions.

#[common_fields {
    common fields here...
}]
enum ...

or fully qualified name if you like

#[diff_enum::common_fields {
    common fields here...
}]
enum ...

Any attributes and comments can be put to the common fields as normal enum fields.

Accessor methods corresponding to common fields are defined. It is a useful helper to access common fields without using pattern match.

For example,

#[common_fields { i: i32 }]
enum E { A, B{ b: bool } }

Generates an accessor method for i as follows:

impl E {
    fn i(&self) -> &i32 {
        match self {
            E::A{ref i, ..} => i,
            E::B{ref i, ..} => i,
        }
    }
}

The attribute macro causes compilation errors in the following cases.

  • When no common field is put
  • When fields in attribute argument is not form of field: type
  • When #[common_fields {...}] is set to other than enum definitions
  • When tuple style enum variant is used in enum definition

License

Distributed under the MIT License.

Dependencies

~1.5MB
~43K SLoC