1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
|
[![Build Status](https://github.com/enarx/flagset/workflows/test/badge.svg)](https://github.com/enarx/flagset/actions)
![Rust Version 1.36+](https://img.shields.io/badge/rustc-v1.36%2B-blue.svg)
[![Crate](https://img.shields.io/crates/v/flagset.svg)](https://crates.io/crates/flagset)
[![Docs](https://docs.rs/flagset/badge.svg)](https://docs.rs/flagset)
![License](https://img.shields.io/crates/l/flagset.svg?style=popout)
# Welcome to FlagSet!
FlagSet is a new, ergonomic approach to handling flags that combines the
best of existing crates like `bitflags` and `enumflags` without their
downsides.
## Existing Implementations
The `bitflags` crate has long been part of the Rust ecosystem.
Unfortunately, it doesn't feel like natural Rust. The `bitflags` crate
uses a wierd struct format to define flags. Flags themselves are just
integers constants, so there is little type-safety involved. But it doesn't
have any dependencies. It also allows you to define implied flags (otherwise
known as overlapping flags).
The `enumflags` crate tried to improve on `bitflags` by using enumerations
to define flags. This was a big improvement to the natural feel of the code.
Unfortunately, there are some design flaws. To generate the flags,
procedural macros were used. This implied two separate crates plus
additional dependencies. Further, `enumflags` specifies the size of the
flags using a `repr($size)` attribute. Unfortunately, this attribute
cannot resolve type aliases, such as `c_int`. This makes `enumflags` a
poor fit for FFI, which is the most important place for a flags library.
The `enumflags` crate also disallows overlapping flags and is not
maintained.
FlagSet improves on both of these by adopting the `enumflags` natural feel
and the `bitflags` mode of flag generation; as well as additional API usage
niceties. FlagSet has no dependencies and is extensively documented and
tested. It also tries very hard to prevent you from making mistakes by
avoiding external usage of the integer types. FlagSet is also a zero-cost
abstraction: all functions are inlineable and should reduce to the core
integer operations. FlagSet also does not depend on stdlib, so it can be
used in `no_std` libraries and applications.
## Defining Flags
Flags are defined using the `flags!` macro:
```rust
use flagset::{FlagSet, flags};
use std::os::raw::c_int;
flags! {
enum FlagsA: u8 {
Foo,
Bar,
Baz,
}
enum FlagsB: c_int {
Foo,
Bar,
Baz,
}
}
```
Notice that a flag definition looks just like a regular enumeration, with
the addition of the field-size type. The field-size type is required and
can be either a type or a type alias. Both examples are given above.
Also note that the field-size type specifies the size of the corresponding
`FlagSet` type, not size of the enumeration itself. To specify the size of
the enumeration, use the `repr($size)` attribute as specified below.
## Flag Values
Flags often need values assigned to them. This can be done implicitly,
where the value depends on the order of the flags:
```rust
use flagset::{FlagSet, flags};
flags! {
enum Flags: u16 {
Foo, // Implicit Value: 0b0001
Bar, // Implicit Value: 0b0010
Baz, // Implicit Value: 0b0100
}
}
```
Alternatively, flag values can be defined explicitly, by specifying any
`const` expression:
```rust
use flagset::{FlagSet, flags};
flags! {
enum Flags: u16 {
Foo = 0x01, // Explicit Value: 0b0001
Bar = 2, // Explicit Value: 0b0010
Baz = 0b0100, // Explicit Value: 0b0100
}
}
```
Flags can also overlap or "imply" other flags:
```rust
use flagset::{FlagSet, flags};
flags! {
enum Flags: u16 {
Foo = 0b0001,
Bar = 0b0010,
Baz = 0b0110, // Implies Bar
All = (Flags::Foo | Flags::Bar | Flags::Baz).bits(),
}
}
```
## Specifying Attributes
Attributes can be used on the enumeration itself or any of the values:
```rust
use flagset::{FlagSet, flags};
flags! {
#[derive(PartialOrd, Ord)]
enum Flags: u8 {
Foo,
#[deprecated]
Bar,
Baz,
}
}
```
## Collections of Flags
A collection of flags is a `FlagSet<T>`. If you are storing the flags in
memory, the raw `FlagSet<T>` type should be used. However, if you want to
receive flags as an input to a function, you should use
`impl Into<FlagSet<T>>`. This allows for very ergonomic APIs:
```rust
use flagset::{FlagSet, flags};
flags! {
enum Flags: u8 {
Foo,
Bar,
Baz,
}
}
struct Container(FlagSet<Flags>);
impl Container {
fn new(flags: impl Into<FlagSet<Flags>>) -> Container {
Container(flags.into())
}
}
assert_eq!(Container::new(Flags::Foo | Flags::Bar).0.bits(), 0b011);
assert_eq!(Container::new(Flags::Foo).0.bits(), 0b001);
assert_eq!(Container::new(None).0.bits(), 0b000);
```
## Operations
Operations can be performed on a `FlagSet<F>` or on individual flags:
| Operator | Assignment Operator | Meaning |
|----------|---------------------|------------------------|
| \| | \|= | Union |
| & | &= | Intersection |
| ^ | ^= | Toggle specified flags |
| - | -= | Difference |
| % | %= | Symmetric difference |
| ! | | Toggle all flags |
## Optional Serde support
[Serde] support can be enabled with the 'serde' feature flag. You can then serialize and
deserialize `FlagSet<T>` to and from any of the [supported formats]:
```rust
use flagset::{FlagSet, flags};
flags! {
enum Flags: u8 {
Foo,
Bar,
}
}
let flagset = Flags::Foo | Flags::Bar;
let json = serde_json::to_string(&flagset).unwrap();
let flagset: FlagSet<Flags> = serde_json::from_str(&json).unwrap();
assert_eq!(flagset.bits(), 0b011);
```
For serialization and deserialization of flags enum itself, you can use the [`serde_repr`] crate
(or implement `serde::ser::Serialize` and `serde:de::Deserialize` manually), combined with the
appropriate `repr` attribute:
```rust
use flagset::{FlagSet, flags};
use serde_repr::{Serialize_repr, Deserialize_repr};
flags! {
#[repr(u8)]
#[derive(Deserialize_repr, Serialize_repr)]
enum Flags: u8 {
Foo,
Bar,
}
}
let json = serde_json::to_string(&Flags::Foo).unwrap();
let flag: Flags = serde_json::from_str(&json).unwrap();
assert_eq!(flag, Flags::Foo);
```
[Serde]: https://serde.rs/
[supported formats]: https://serde.rs/#data-formats
[`serde_repr`]: https://crates.io/crates/serde_repr
|