peternator7 / strum Goto Github PK
View Code? Open in Web Editor NEWA small rust library for adding custom derives to enums
Home Page: https://crates.io/crates/strum
License: MIT License
A small rust library for adding custom derives to enums
Home Page: https://crates.io/crates/strum
License: MIT License
AsRef<str>
provides string without memory allocation, so I want it.
It would be nice to be able to iterate over enums in reverse order.
Currently I have to use the workaround my_enum::iter().collect::<Vec<_>>().iter().rev()
, which is quite ugly and probably less performant.
This could be implemented by making the IntoEnumIter::iter function return a DoubleEndedIterator to allow code like this:
#[derive(EnumIter)]
enum Colors {Red, Green, Blue}
use strum::IntoEnumIterator;
for color in Colors::iter().rev() {
// Do something with color.
}
This approach might incur a runtime cost, because we would have to add another field to the iterator struct (a second index for next_back
similar to the existing idx
) even if the user does not call next_back
.
Another possible way to implement this is to add a new method, that returns a reverse or double-ended iterator.
Are you interested in adding this functionality?
If an enum is called FooBarBaz
, EnumCount
will name the constant FOOBARBAZ_COUNT
. I believe FOO_BAR_BAZ_COUNT
would be more expected. This can be achieved by heck
, which is already a dependency of strum
.
Using strum with rust 2018 probably needs a use strum<something>::strum;
because by default this error appears:
error[E0658]: The attribute `strum` is currently unknown to the compiler and may have meaning added to it in the future (see issue #29642)
--> /home/xav/lfgt/target/debug/build/lfgt-3925bf91df78c23d/out/equipments.rs:3:5
|
3 | #[strum(serialize = "agt")]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
Would it be possible to add the remedy to the README.md ?
I would like to be able to do something like this
#[derive(Display)]
#[strum("series.{}", serialize_all = "snake_case")]
struct Series {
Media
}
assert_eq!(Series::Media.to_string(), "series.media".to_string());
The attribute could also look like this
#[strum(format = "series.{}", serialize_all = "snake_case")]
The implementation would be relatively simple (I think?):
use syn::LitStr;
// parse this from the attribute:
let format_string = "series.{}".to_string();
// the serialized field name
let field_name = "media".to_string();
let lit_str = LitStr::new(&format_string.replace("{}", &field_name), Span::call_site());
quote {
impl ::std::fmt::Display for Series {
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
match &self {
Self::Media => f.write_str(#lit_str),
}
}
}
}
Right now --when no matching variant is found-- (parsing a string into an enum using the derived from_str
) a constant string error is returned saying "Matching variant not found"
. There are two improvements that can be done on this:
Better default: A better default would probably be something like "expected 'string1', 'string2', 'string3' or 'string4'"
. Even though it costs to iterate over the options, it doesn't matter, because it would only happen when no valid string is found.
Custom error: The user should be able to specify a custom error message, something like:
#[derive(EnumString)]
#[strum(onerror="We like you, but can't understand what you wrote, please use 'string1' or 'string2'")
enum Foo {
#[strum(serialize="string1")]
Option1,
#[strum(serialize="string2")]
Option2,
}
** special token: You could even provide a special token for the onerror
keyword so that the user doesn't have to repeat the options all the time. Something like onerror="We like you, but can't understand what you wrote, please use {opts}"
.
Sometimes we need to convert isize
to an enum variant with explicit discriminator.
enum/c_like
For example, high level API converts error code to an error enum variant. The feature will be useful when it comes to unsafe and FFI.
The solution is to implement a proc macro like:
proc_macro_derive(EnumFromIsize)
The generated code will look like:
impl TryFrom<isize> for MyEnum {
type Error = StrumCustomError;
fn try_from(u: isize) -> Result<MyEnum, StrumCustomError> {
match u {
// ......
}
}
}
The Display
procedural macro unfortunately conflicts with @withoutboats' display_derive
procedural macro. In some code I'm working with on a personal project, the following will compile when #[macro_use]
ing only display_derive
, but not in conjunction with strum_derive
:
#[derive(Clone, Debug, Deserialize, Display, Eq, Hash, PartialEq, Serialize)]
#[display(fmt = "{}", _0)]
pub struct MessageId(pub String);
Both of these crates are great additions to the ecosystem, in my opinion. It's a shame that we don't have macros as first-class items yet and that I have to choose between them. Perhaps disabling Display
with a feature flag would be best for now?
EDIT: It's also possible to use the right crate's definition by making the desired derive be imported LAST...but that's a hacky workaround.
For struct like:
#[derive(Display)]
enum Type {
One,
Two,
}
The call:
println!("_ {:<20} _", Type::One);
Will print:
_ One _
But this expected:
_ One _
The workaround today only to convert it to string first:
println!("_ {:<20} _", Type::One.as_ref()); // and derive `AsRefStr` for enum needed
Is there any way to provide a "catchall" option for EnumString that will store the parsed string into a variant?
Eg:
enum Fruit {
Strawberry,
Banana,
Other(String),
}
Thanks!
When EnumCount was introduced, there was a comment that the count() function could be const once the required feature get stabilized in Rust. It seems to me it is possible now, but I'm not a Rust expert yet, so I could be wrong. What is your opinion of this?
As a side effect, this could get rid of the X _COUNT constants polluting the public namespace, but I don't know if you'd want to remove them due to backwards compatibility. However, according to semver, version 0.x.y does not provide any guarantees, so the users should not have any assumptions anyway.
When trying to use full-qualified paths for the custom derive, none of the attributes are applied.
For example:
#[allow(dead_code)]
#[derive(Debug, Eq, PartialEq, strum_macros::EnumDiscriminants)]
#[strum_discriminants(name(SplitAttributesBoo), derive(strum_macros::Display))]
#[strum_discriminants(derive(strum_macros::EnumIter))]
enum SplitAttributes {
Variant0(bool),
Variant1(i32),
}
I digged a bit into the code and it actually looks like syn
doesn't report the full path in that case. I googled around a bit and found a similar problem here: rust-lang/rust#55168
But not sure how relevant that actually is.
I created a PR that reproduces the issue here: #54.
The derive for EnumDiscriminant is helpful, but it doesn't provide a way to convert back to the original enum whose variants it enumerates. Is this something that can be implemented?
Given that I have this enum:
enum SpecialSeries {
DA,
KT,
LD,
}
Now I want to build this string
"DA|KT|LD"
in order to use as pattern for RegEx. How should I code with strum
? (I don't write Rust often and always forget things when I try to learn Rust again).
Thank you.
EnumIter
and EnumString
both work by using Default::default()
values for any fields on an enum variant. I have a use case where I want to match on the enum's discriminant, but the fields are !Default
.
To do this, I propose a #[derive(EnumDiscriminants)]
as well as the attribute #[strum_discriminants(derive(..))]
, which will have the following effect:
struct NonDefault;
#[derive(EnumDiscriminants)]
#[strum_discriminants(
name(HelloVariants),
derive(EnumString, EnumIter),
strum(serialize_all = "snake_case")
)]
enum Hello {
Variant0,
Variant1(NonDefault),
Variant2 { a: NonDefault },
}
// produces:
/// Auto-generated discriminant variants for the `Hello` enum.
#[derive(Clone, Copy, Debug, PartialEq, Eq, EnumString, EnumIter)]
#[strum(serialize_all = "snake_case")]
enum HelloVariants {
Variant0,
Variant1,
Variant2,
}
impl From<Hello> for HelloVariants {
fn from(val: Hello) -> HelloVariants {
match val {
Hello::Variant0 => HelloVariants::Variant0,
Hello::Variant1(..) => HelloVariants::Variant1,
Hello::Variant2 { .. } => HelloVariants::Variant2,
}
}
}
impl<'_enum> From<&'_enum Hello> for HelloVariants {
fn from(val: &'_enum Hello) -> HelloVariants {
match *val {
Hello::Variant0 => HelloVariants::Variant0,
Hello::Variant1(..) => HelloVariants::Variant1,
Hello::Variant2 { .. } => HelloVariants::Variant2,
}
}
}
FromStr doesn't support lifetimes, and I'd like the following to be possible:
#[derive(Debug, PartialEq, Eq, Clone, Copy, strum_macros::AsRefStr, strum_macros::EnumString)]
pub enum ChunkType<'a> {
#[strum(to_string = "nMC")]
MChat,
#[strum(to_string = "nM")]
MPlayer,
#[strum(to_string = "nMR")]
MRequest,
#[strum(to_string = "nM?")]
MServerInfo,
#[strum(to_string = "nU")]
UUpdate,
#[strum(to_string = "\r\n")]
Eof,
#[strum(to_string = "nH")]
HHead,
#[strum(default = "true")]
Unknown(&'a str),
}
The following code doesn't work as expected
#[derive(Display)]
#[strum(serialize_all = "SCREAMING-KEBAB-CASE")]
enum Example {
Aes128
}
assert_eq!(Example::Aes128, "AES-128".to_string());
instead strum generates:
"AES128"
IntoEnumIterator
does not have a constraint on Iterator: Iterator<Item=Self>.
When writing generic code on any IntoEnumIterator
, it forces us to write the very verbose:
where
<Self as IntoEnumIterator>::Iterator: Iterator<Item = Self>,
though that behaviour could be enforced as it is documented on the trait.
We could avoid having to write this by adding an Iterator<Item=Self>
bound on the associated type Iterator
of IntoEnumIterator
.
Although the docs state compatibility with 1.31.0
error[E0658]: use of unstable library feature 'iter_nth_back'
--> src/main.rs:139:75
|
139 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Hash, EnumIter)]
| ^^^^^^^^
|
= note: for more information, see https://github.com/rust-lang/rust/issues/56995
error: aborting due to previous error
Happened with all variations I tried it with
There should be a derive proc-macro alternative to the macro_rules enum_from_primitive!{..}
because it doesn't play well with other such macros like custom_derive!{..}
.
I think it would make sense to have it as part of this crate, because it already contains many useful derives for enums.
You can't #[derive(EnumIter)]
with #![deny(missing docs)]
Hi!
I've noticed that using strum's derive EnumDiscriminants
with some serde variant attributes, results in a compiler error.
I'm not sure of this is an issue in strum, in serde, or both.
Here's a program to reproduce the issue:
[dependencies]
serde = { version = "1.0", features = ["derive"] }
strum = "0.16.0"
strum_macros = "0.16.0"
extern crate strum;
#[macro_use] extern crate strum_macros;
extern crate serde;
use std::str::FromStr;
use serde::{Serialize, Deserialize};
#[derive(Serialize, Deserialize, Debug, EnumDiscriminants)]
#[serde(tag = "type")]
enum Message {
#[serde(rename = "request" )]
Request { id: String, method: String, params: u8 },
#[serde(rename = "response" )]
Response { id: String, result: u8 },
}
fn main() {
println!("Hello, world!");
}
error[E0658]: the attribute `serde` is currently unknown to the compiler and may have meaning added to it in the future
--> src/main.rs:12:5
|
12 | #[serde(rename = "request" )]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: for more information, see https://github.com/rust-lang/rust/issues/29642
error[E0658]: the attribute `serde` is currently unknown to the compiler and may have meaning added to it in the future
--> src/main.rs:14:5
|
14 | #[serde(rename = "response" )]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: for more information, see https://github.com/rust-lang/rust/issues/29642
warning: unused import: `std::str::FromStr`
--> src/main.rs:5:5
|
5 | use std::str::FromStr;
| ^^^^^^^^^^^^^^^^^
|
= note: `#[warn(unused_imports)]` on by default
error: aborting due to 2 previous errors
For more information about this error, try `rustc --explain E0658`.
The program compiles successfully if the lines #[serde(rename = "response" )]
are removed.
It would be nice if strum could incapsulate a bit more its implementation details.
In particular, I'd like to avoid having strum::IntoEnumIterator
to be imported by all consumers, and instead use the IntoIterator
one from stdlib.
That one however is a consuming method, so I'm not sure if it can be transparently adopted. An alternative would be to drop the trait completely and just expose a naked iter()
like most collection types do.
Hiya! Thanks for this awesome crate ๐.
There are a number of fixes in master
that help me along my quest โ๏ธ.
Please could you release the crate ๐ฆ, or is there something that must first be done (like that open PR)?
Otherwise crates.io tarball does not contain those files.
Thanks!
The following piece of code:
#[macro_use]
extern crate strum_macros;
#[derive(Clone, Copy, Display, Debug, Serialize)]
#[strum(serialize_all = "snake_case")]
pub enum DataName {
SecretHash,
Expiry,
RedeemIdentity,
RefundIdentity,
TokenQuantity,
TokenContract,
}
Compiles with 0.14.0
but does not compile with 0.15.0
:
error[E0658]: The attribute `strum` is currently unknown to the compiler and may have meaning added to it in the future (see issue #29642)
--> vendor/blockchain_contracts/src/lib.rs:16:3
|
16 | #[strum(serialize_all = "snake_case")]
| ^^^^^
|
= help: add #![feature(custom_attribute)] to the crate attributes to enable
error: cannot find derive macro `Display` in this scope
--> vendor/blockchain_contracts/src/lib.rs:15:23
|
15 | #[derive(Clone, Copy, Display, Debug, Serialize)]
|
May I suggest to move the readme examples to rust-docs to avoid this kind of issue?
I'd like to be able to derive functions for each variant: is_variant1()
, etc. Much like is_some()
, is_none()
, is_ok()
, etc.
Is that supported? Feature request-able?
rename_all
from serde
accepts kebab-case
, camelCase
, PascalCase
, etc. The option string is just written in what itself representing to.
But serialize_all
accepts all options in snake case, like kebab_case
or shouty_snake_case
, which can be sometimes misleading.
I think we should accept and prefer serde
-like option string, while the snake case options can still be used for compatibility, maybe with a warning.
It would be great to have a serialize_all
feature ร la serde rename_all
. https://serde.rs/container-attrs.html
This crate may be interesting: https://crates.io/crates/heck
Maybe with a spec and some insight, I might try to do it myself if I found the time.
When you derive on an enum, sometimes you want to implement some From<XXXNotFound>
for some other error types, with maybe more explicit messages. It would be great if we could have an attribute that enables us to set the function that would be used to build the error from the original &str
.
e.g.
#[derive(EnumString)]
#[strum(parse_err_ty = "SomeEnumNotFound", parse_err_fn = "some_enum_not_found_err")]
pub enum SomeEnum {
A,
B,
}
pub struct SomeEnumNotFound;
fn some_enum_not_found_err(_not_found: &str) -> SomeEnumNotFound { SomeEnumNotFound }
generating
impl FromStr for SomeEnum {
type Err = SomeEnumNotFound;
fn from_str(s: &str) -> Result<SomeEnum, Self::Err> {
match s {
"A" => A,
"B" => B,
other => some_enum_not_found_err(other),
}
}
}
If parse_err_fn
is not specified but parse_err_ty
is, we could fallback on From<strum::ParseError>
.
First of all, thanks for this crate!
I'm refactoring some code to use the discriminants and would like to keept the documentation and doctests on the discriminant enum. Is there any way I can do that?
I was briefly looking at this crate, and I think I would love to see an automatic to_string()
implementation and some way to link it to the current attributes-based approach employed for from_string()
. This would allow for roundtrips Enum <-> String.
I'm actually thinking about something similar to serde ones plus additional aliases:
#[derive(EnumString)]
enum Color {
#[strum(rename="blue",alias="b")]
Blue,
...
}
impl std::str::FromStr for Color {
fn from_str(s: &str) -> ::std::result::Result<Color, Self::Error> {
match s {
"blue" | "b" => ::std::result::Result::Ok(Color::Blue),
...
}
}
}
impl fmt::Display for Color {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Color::Blue => write!(f, "blue"),
...
}
}
}
There are also a bunch more interesting methods in https://docs.rs/enum_derive/0.1.7/enum_derive/#overview.
Hi,
Would be awesome if you could publish a v0.16.0
of Strum to https://crates.io/crates/strum
, as the latest version there is v0.15.0
from March, which for example doesn't have EnumVariantNames
(#56)
Right now I have to do this to get EnumVariantNames
in my project:
[dependencies]
strum = { git = "https://github.com/Peternator7/strum", rev = "efed58502a40ac101691068bbc6c20a0b351f3fd" }
strum_macros = { git = "https://github.com/Peternator7/strum", rev = "efed58502a40ac101691068bbc6c20a0b351f3fd" }
(Also would be nice with tags in Github that corresponds to the releases on Crates.io.)
Thanks!
As it currently is, Strum's EnumIterator trait only allows for the construction of enum iterators from the first variant, which makes it horribly inefficient (relatively speaking) to perform incremental iteration of enum variants. A means of constructing this iterator so it starts from an existing enum would resolve this.
Would be useful for answering this Stack Overflow question.
When will it be possible to use EnumProperty
with other types than String?
Are only literals supported or full expressions? :)
Lines 113 to 117 in b6e4e66
Also the same type is in the Readme.
Maybe I'm just dumb, but is there a way to do something like:
enum Compression {
Lz4,
Gzip(u8),
}
Compression::Gzip(9).to_string() # returns gzip-9
Compression::from_str("gzip-2") # returns Compression::Gzip(2)
I wonder how you manage the non-descriptive names like as_static
, as_ref
, when you have an enum that, e.g. describes a state:
enum MyState {
Position1(i32),
GameOver(String),
}
Having my_state.as_ref()
or my_state.as_static()
looks odd to me when I really want to express my_state.as_variant_name()
. Currently, I have to write a 4-line wrapper around as_static
just to make my API sane, but I still have a confusing AsStaticRef
implementation in the documentation.
This is a small papercut, but I still want to bring it in.
I would prefer having AsEnumVariantName
trait with as_enum_variant_name
or as_variant_name
method.
This project does have a CHANGELOG.md
file (๐), but it seems like the new v0.14.0 release is missing from it. What were the changes that went into that release? Were there any breaking changes?
I have some default variant that contains a string, like:
#[derive(Display, EnumString)]
FooEnum {
#[strum(default="true")]
Bar(String)
}
but if i do:
FooEnum::Bar("baz".into()).to_string();
i get "Bar"
not "baz"
so then there's no way to round trip it back into the original FooEnum
I noticed that the EnumVariantNames
is shown in the README
(and it is pretty useful!), but it is not available on the last release. Could a minor version bump be worth in order to use this feature in a stable release?
Currently deriving EnumProperty
results in a bunch of get*() -> Option<*>
. This works quite well in general, but it moves some property checking to runtime option-handling.
What I'd like to see provided in this crate is some way of opting-in to stronger guarantees and typing of properties, such that I could:
get_prop() -> PropType
generatedThe usestory for this is: complex enums declarations (with attributes) can become quite long and some variants can end up with missing prop-attributes or with copy-paste type mistakes. Those would only be catched when unwrapping the Option at runtime, but the compiler should be able to sanity-check this at buildtime. Also it would make the getter more specifically named and remove the needs of Option wrapping.
Some hypotetical syntax (but feel free to design your one discarding this) could be:
#[derive(EnumProperty)]
#[strum(props(room: i32))]
enum Class {
#[strum(props(teacher="foo", room=101))]
History,
#[strum(props(room=2))]
Mathematics,
#[strum(props(room=-1))]
Science,
}
fn main() {
let history = Class::History;
assert_eq!(101, history.get_room());
}
Where adding a variant without the room
prop or with a non-matching type would fail type-checking / compilation.
What about generates something like:
impl Into<&'static str> for $Name {
fn into(self) -> &'static str {
match self {
$($Name::$Variant => $StrVar),*
}
}
}
instead of strum::AsStaticStr
or in additional to strum::AsStaticStr
?
In the documentation and Wiki for EnumString , there is
//The generated code will look like:
but there is no actual code example how to use it.
Something like this:
use std::str::FromStr;
let color_str="Red";
let color_variant = Color ::from_str(color_str).unwrap();
assert!(Color:Red, color_variant);
Why is the documentation/examples using the old syntax:
extern crate strum;
#[macro_use] extern crate strum_macros;
instead of the new:
use strum;
use strum_macros
hello!
we're currently updating proc-macro2
, quote
, and syn
in Debian (which all recently reached 1.0). An update for strum-macros with those versions would be great (we're also able to cherrypick a patch if needed).
Thanks!
I wrote this proc-macro because I needed it (for mapping Enum cases from different enums to Vec regions, making the Vec len equal to the combined number of all cases) and I think it would make sense to include it in this crate, because it already contains many useful derives for enums.
https://github.com/Boscop/enum-count/blob/master/src/lib.rs
https://github.com/Boscop/enum-count-derive/blob/master/src/lib.rs
It allows stuff like this:
#[derive(EnumCount)] enum Aa { }
#[derive(EnumCount)]
enum Bb { B0 = AA_COUNT as _ }
#[repr(u8)]
#[derive(EnumCount)]
enum Cc { C0 = AA_COUNT as _, C1 = BB_COUNT as _ }
#[repr(usize)]
#[derive(EnumCount)]
enum Dd { D0 = AA_COUNT, D1 = BB_COUNT, D2 = CC_COUNT }
fn main() {
println!("{} {} {} {}", Aa::count(), BB_COUNT, Cc::count(), DD_COUNT);
}
(The constant is necessary for it to be used in other enums and static array lens etc.)
I'm interested in porting the FromIndex
and ToIndex
traits from enum_traits
to this crate.
However I only care about the from_index
and index
methods. The other methods don't seem useful?
My reasoning is that enum_traits
is unmaintained and its functionality seems similar enough to this crate.
What are your thoughts? Is this within scope of the project?
Rustdoc for ToString
says:
ToString
shouldn't be implemented directly:Display
should be implemented instead, and you get theToString
implementation for free.
So derive(ToString)
should implement std::fmt::Display
instead of std::string::ToString
(and by this, std::string::ToString
would be automatically implemented).
Then derive(Display)
or some other appropriate names would be preferable to derive(ToString)
.
This may be breaking changes (possibly rename ToString
and to_string
), but automatic derive for Display
will be very useful.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.