Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a bitflags! macro #13072

Merged
merged 5 commits into from
Apr 30, 2014
Merged

Add a bitflags! macro #13072

merged 5 commits into from
Apr 30, 2014

Conversation

brendanzab
Copy link
Member

The bitflags! macro generates a struct that holds a set of C-style bitmask flags. It is useful for creating typesafe wrappers for C APIs.

For example:

#[feature(phase)];
#[phase(syntax)] extern crate collections;

bitflags!(Flags: u32 {
    FlagA       = 0x00000001,
    FlagB       = 0x00000010,
    FlagC       = 0x00000100,
    FlagABC     = FlagA.bits
                | FlagB.bits
                | FlagC.bits
})

fn main() {
    let e1 = FlagA | FlagC;
    let e2 = FlagB | FlagC;
    assert!((e1 | e2) == FlagABC);   // union
    assert!((e1 & e2) == FlagC);     // intersection
    assert!((e1 - e2) == FlagA);     // set difference
}

//! # Example
//!
//! ~~~rust
//! bitset!(Flags: u32 {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this example actually pass tests?

Don't you need to import the macro with a #[phase(syntax)] extern crate collections;?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Weird, all the tests passed. Doesn't seem like it ran:

run doc-crate-collections [x86_64-apple-darwin]

running 2 tests
test hashmap::HashMap_0 ... ok
test lru_cache_0 ... ok

Maybe I should remove rust in ~~~rust?

Come to think of it, how would I do this test? I thought you couldn't do #[phase(syntax)] extern crate collections; from within a doc test? Also, the bitset! macro needs to be invoked at the item level.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, yeah, maybe it's the rust or maybe its the ~~~ (most tests use

```

)

But if you put a fn main() {} in the test somewhere, rustdoc will no longer automatically insert the wrapping function main, so I think

#[feature(phase)];
#[phase(syntax)] extern crate collections;

/* bitset!() declaration */

fn main() {
    let e1 = FlagA | FlagC;
    // etc.
}

might work.

@huonw
Copy link
Member

huonw commented Mar 22, 2014

Oh, another naming thing: the word "bitset" sounds like it would have functionality pretty similar to our BitV type. And "bitset" is actually the name that e.g. Java and C++ use for the equivalent of our BitV type.

Maybe this could be called bitflags or something.

@brendanzab brendanzab changed the title Add a bitset! macro Add a biflags! macro Mar 22, 2014
@brendanzab
Copy link
Member Author

Rebased.

@brendanzab brendanzab changed the title Add a biflags! macro Add a bitflags! macro Mar 22, 2014
@brendanzab
Copy link
Member Author

I added some documentation showing how bitflags can be extended with trait and type implementations.

//! - `insert`: inserts the specified flags in-place
//! - `remove`: removes the specified flags in-place

#[macro_escape];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't really needed since nothing else in libcollections uses bitflags!.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, so that isn't needed for exporting macros?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#[macro_escape] lets the definition escape to the next scope level (the crate root in this case). It's unrelated to #[macro_export].

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ahh, I knew about the escaping bit, but yeah, wasn't sure about the exporting.

@alexcrichton
Copy link
Member

This seems pretty interesting, but is there a reason that the existing EnumSet collection doesn't suffice? Could EnumSet be improved to support this functionality? Right now we don't have a lot of apis that are macro-generated, and it would be nice to stay that way, but I may be missing something that this enables that you can't do with EnumSet

@brendanzab
Copy link
Member Author

  • One of the great things about bitflags is how nice the invocations look through the use of the bitwise operators. The API generated by bitflags! allows you to use the operators as in C code. EnumSet defines them but they are of little utility in practice.
  • EnumSet works only works by casting to uints, rather than with other integer types.
  • It is awkward to have yet another trait, especially one as limited in its utility as CLike.
  • I don't like adding transmutes into enums if I can help it, but the Clike trait just goads me into it.
#[repr(u32)]
enum InitFlags {
    InitTimer               = 0x00000001,
    InitAudio               = 0x00000010,
    InitVideo               = 0x00000020,
    InitJoystick            = 0x00000200,
    InitHaptic              = 0x00001000,
    InitGamecontroller      = 0x00002000,
    InitEvents              = 0x00004000,
    InitNoparachute         = 0x00100000,
    InitEverything          = InitTimer as u32
                            | InitAudio as u32
                            | InitVideo as u32
                            | InitEvents as u32
                            | InitJoystick as u32
                            | InitHaptic as u32
                            | InitGamecontroller as u32,
}

impl CLike for InitFlags {
    fn to_uint(&self) -> uint {
        *self as uint
    }

    fn from_uint(v: uint) -> InitFlags {
        unsafe { cast::transmute(v as u32) }
    }
}

// ...

let mut flags = EnumSet::empty();
flags.add(sdl2::InitVideo);
flags.add(sdl2::InitEvents);
sdl2::init(flags)
bitflags!(InitFlags: Uint32 {
    InitTimer               = 0x00000001,
    InitAudio               = 0x00000010,
    InitVideo               = 0x00000020,
    InitJoystick            = 0x00000200,
    InitHaptic              = 0x00001000,
    InitGamecontroller      = 0x00002000,
    InitEvents              = 0x00004000,
    InitNoparachute         = 0x00100000,
    InitEverything          = InitTimer.bits
                            | InitAudio.bits
                            | InitVideo.bits
                            | InitEvents.bits
                            | InitJoystick.bits
                            | InitHaptic.bits
                            | InitGamecontroller.bits
})

// ...

sdl2::init(sdl2::InitVideo | sdl2::InitEvents)

@brendanzab
Copy link
Member Author

I also think that the iterator might be broken, for example:

let mut flags = collections::EnumSet::empty();
flags.add(InitJoystick);
flags.add(InitVideo);
flags.add(InitHaptic);
for flag in flags.iter() {
    println!("{}", flag);
}

Prints:

InitEverything
InitVideo

Of course that does not mean that the implementation could not be fixed.

@alexcrichton
Copy link
Member

Some of your concerns can be addressed by updating the EnumSet api. For example, it should take T: FromPrimitive + ToPrimitive rather than CLike which means you can use deriving. Other concerns could not be addressed easily, such as changing the underlying representation of the bit set from a 32-bit number to a 64-bit number (or similarly).

I'm worried about having two bit sets, and I'd like to reconcile the two of them before landing.

@brendanzab
Copy link
Member Author

Removing the iterator, which seems to be broken, would remove the need for FromPrimitive. But I still find it ugly for the end user, having to jump through the hoops of using the add method. In its current state I wouldn't want to use the current EnumSet for one of my libraries. And the representation is a problem.

@brendanzab
Copy link
Member Author

We could reccomend that library implementors implement specific operator overloads for convenience. For example you could have T | EnumSet<T> -> EnumSet<T>. But associativity would be 'surprising' for end users, for example EnumSet<T> | T would not work. So I would rather not do that.

@SiegeLord
Copy link
Contributor

I think this is a great idea. I have actually came up with a eerily similar implementation for my own libraries: definition/usage.

My primary motivation was the bitwise operators interface, but it seems to me if I actually tried using EnumSet I'd have run into the remaining issues bjz mentioned.

@bvssvni
Copy link

bvssvni commented Mar 24, 2014

EnumSet uses a phantom type to bind the usage, but since the generic constraint is decoupled from the type declaration, it becomes more confusing.

From a user-friendly perspective I think bitflags is better. It took me 3 seconds to understand how bitflags is used, but I am still pondering whether EnumSet doesn't have any caveats I am not aware about.

@brendanzab
Copy link
Member Author

From a user-friendly perspective I think bitflags is better. It took me 3 seconds to understand how bitflags is used, but I am still pondering whether EnumSet doesn't have any caveats I am not aware about.

Was that from looking at the code, or my documentation? Macros can be pretty hard to document.

@bvssvni
Copy link

bvssvni commented Mar 24, 2014

EnumSet lacks code example, which could clarify the usage quite a bit. I learned the usage of bitflags by looking at the example and the documentation gives better in-depth explanation. I like that you can specify the type and not rely on uint which EnumSet uses.

I think EnumSet is useful in cases where one can not alter the enum declaration. It can also be used to generate uint (with CLike) as a meta type for enum variants, which bitflags does not support.

It seems to me that the use cases does not completely overlap. Maybe we could have both?

@brendanzab
Copy link
Member Author

I would just like to add a severe limitation to the bitflags! macro that I just thought of: the inability to add doc comments to the variants. :(

@pczarn
Copy link
Contributor

pczarn commented Mar 25, 2014

I have basically a similar implementation as well (just implemented Sub and is_zero after I saw your code.) I think you should make negation possible.

Recursive macros can match the head/tail of the list of flags and optionally default to the value of the previous flag shifted by one.

@brendanzab
Copy link
Member Author

Now that is cool! If only we could fix the documentation issue...

@aturon
Copy link
Member

aturon commented Apr 29, 2014

@bjz: this PR was discussed in the weekly meeting today, and consensus was that we should merge it, but as part of libstd. This is because we want to be able to provide type-safe APIs in libstd that are C-compatible. (Ultimately libstd and libcollections are likely to be refactored making this moot.)

We can improve the documentation aspect later on, once sufficient macro support is available.

Can you please update to move to libstd (and take care of any bitrot)? Then we will review and merge.

@brendanzab
Copy link
Member Author

@aturon sure thing!

The `bitflags!` macro generates a `struct` that holds a set of C-style bitmask flags. It is useful for creating typesafe wrappers for C APIs.

For example:

~~~rust
#[feature(phase)];
#[phase(syntax)] extern crate collections;

bitflags!(Flags: u32 {
    FlagA       = 0x00000001,
    FlagB       = 0x00000010,
    FlagC       = 0x00000100,
    FlagABC     = FlagA.bits
                | FlagB.bits
                | FlagC.bits
})

fn main() {
    let e1 = FlagA | FlagC;
    let e2 = FlagB | FlagC;
    assert!((e1 | e2) == FlagABC);   // union
    assert!((e1 & e2) == FlagC);     // intersection
    assert!((e1 - e2) == FlagA);     // set difference
}
~~~
@brendanzab
Copy link
Member Author

Rebased.

//! - `intersects`: `true` if there are flags common to both `self` and `other`
//! - `contains`: `true` all of the flags in `other` are contained within `self`
//! - `insert`: inserts the specified flags in-place
//! - `remove`: removes the specified flags in-place
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now that we can put documentation on macros themselves, I wonder if this would be best suited on the macro itself.

I suppose if you search for bitflags you'll probably hit the module before the macro, though.

@alexcrichton
Copy link
Member

r=me with the updated examples (removing the extern crate collections)

This will allow us to provide type-safe APIs in libstd that are C-compatible.
bors added a commit that referenced this pull request Apr 30, 2014
The `bitflags!` macro generates a `struct` that holds a set of C-style bitmask flags. It is useful for creating typesafe wrappers for C APIs.

For example:

~~~rust
#[feature(phase)];
#[phase(syntax)] extern crate collections;

bitflags!(Flags: u32 {
    FlagA       = 0x00000001,
    FlagB       = 0x00000010,
    FlagC       = 0x00000100,
    FlagABC     = FlagA.bits
                | FlagB.bits
                | FlagC.bits
})

fn main() {
    let e1 = FlagA | FlagC;
    let e2 = FlagB | FlagC;
    assert!((e1 | e2) == FlagABC);   // union
    assert!((e1 & e2) == FlagC);     // intersection
    assert!((e1 - e2) == FlagA);     // set difference
}
~~~
@bors bors merged commit 63ee7bb into rust-lang:master Apr 30, 2014
@brendanzab brendanzab deleted the bitset branch April 30, 2014 20:43
@aturon aturon mentioned this pull request May 1, 2014
@jcmoyer
Copy link
Contributor

jcmoyer commented May 7, 2014

Is there any chance this will support Shl, Shr, and Not in the future? Also, an inverse of empty would be nice. I'd be happy to submit a PR for these since I'd like to replace my macro in rust-sdl2 with this one, but it's missing a few essential operations.

@thestinger
Copy link
Contributor

@jcmoyer: I'm not sure that Shl and Shr make sense, since it's not an arithmetic type. It's already implementing subtraction as a set would rather than an integer. The set complement (Not) makes sense, but I'm not sure what it should do for signed types.

@jcmoyer
Copy link
Contributor

jcmoyer commented May 7, 2014

@thestinger: I'd expect Not to invert all of the bits in the wrapped value regardless of it's signedness. Is there a reason why this wouldn't be valid for signed types? The macro is explicitly for dealing with numbers at the bit level where you typically don't care about this sort of thing.

As for Shl and Shr: shouldn't BitAnd and BitOr implementations be dropped for the same reason? If one operator is overloaded to do something odd, you can't expect any other operators to do "the right thing."

@thestinger
Copy link
Contributor

BitAnd and BitOr do have a proper meaning on sets, and that applies to Not too.

My point was that since subtraction is performing a set difference, arithmetic/bitwise operators without a set equivalent don't make much sense.

@jcmoyer
Copy link
Contributor

jcmoyer commented May 7, 2014

@thestinger: Makes sense. Thanks for clarifying.

bors added a commit that referenced this pull request May 14, 2014
I feel that this is a very vital, missing piece of functionality. This adds on to #13072.

Only bits used in the definition of the bitflag are considered for the universe set. This is a bit safer than simply inverting all of the bits in the wrapped value.

```rust
bitflags!(flags Flags: u32 {
    FlagA       = 0x00000001,
    FlagB       = 0x00000010,
    FlagC       = 0x00000100,
    FlagABC     = FlagA.bits
                | FlagB.bits
                | FlagC.bits
})

...

// `Not` implements set complement
assert!(!(FlagB | FlagC) == FlagA);
// `all` and `is_all` are the inverses of `empty` and `is_empty`
assert!(Flags::all() - FlagA == !FlagA);
assert!(FlagABC.is_all());
```
@danthedeckie
Copy link

For future reference, this was later removed from std
231d9e7
and currently recommended to use the bitflags crate.

JohnTitor pushed a commit to JohnTitor/rust that referenced this pull request Sep 6, 2022
…jonas-schievink

feat: Add a "Unmerge match arm" assist to split or-patterns inside match expressions

Fixes rust-lang#13072.

The way I implemented it it leaves the `OrPat` in place even if there is only one pattern now but I don't think something will break because of that, and when more code will be typed we'll parse it again anyway. Removing it (but keeping the child pattern) is hard, I don't know how to do that.
flip1995 pushed a commit to flip1995/rust that referenced this pull request Jul 25, 2024
Misc refactorings part 5

`toplevel_ref_arg` gets a small fix so it can be allowed on function arguments. Otherwise just some rearrangements.

changelog: none
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.