Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Bit flags

C represents a set of independent options by assigning each option a bit:

#define FIND_URLS             (1u << 0)
#define FIND_TAGS             (1u << 1)
#define FIND_CASE_INSENSITIVE (1u << 2)

size_t find_bookmarks(const struct Bookmark *bookmarks, size_t len,
                      const char *query, uint32_t flags);

Callers combine options with | and test them with &. The representation is compact and crosses an ABI easily, but uint32_t accepts unrelated constants, unknown bits, and combinations the operation may not support.

Name the set of valid bits

The bitflags crate generates a transparent newtype, constants, bitwise operators, and set-like methods:

use bitflags::bitflags;

bitflags! {
    #[repr(transparent)]
    #[derive(Clone, Copy, Debug, PartialEq, Eq)]
    struct FindFlags: u32 {
        const URLS = 1 << 0;
        const TAGS = 1 << 1;
        const CASE_INSENSITIVE = 1 << 2;
    }
}

The storage width matches the C header, and #[repr(transparent)] gives the wrapper the layout of that u32. Within Rust, contains requires every bit in its argument, while intersects asks whether any bit overlaps. insert and remove mutate a set, and bits() returns the raw representation for C.

Named bits don’t automatically make every combination meaningful. If a search must inspect at least URLs or tags, validate that separately:

use bitflags::bitflags;
bitflags! { struct FindFlags: u32 {
  const URLS = 1; const TAGS = 2; const CASE_INSENSITIVE = 4;
} }
fn validate(flags: FindFlags) -> Result<FindFlags, &'static str> {
    if !flags.intersects(FindFlags::URLS | FindFlags::TAGS) {
        return Err("select URLS, TAGS, or both");
    }
    Ok(flags)
}

Choose an unknown-bit policy

Converting a raw C integer requires an explicit compatibility decision:

  • from_bits(raw) rejects any unknown bit;
  • from_bits_truncate(raw) discards unknown bits;
  • from_bits_retain(raw) preserves them.

Rejecting is appropriate when silently ignoring an unknown option would do the wrong thing. Retaining can be useful for a value that must round-trip to a newer library even though this version doesn’t interpret every bit. Truncating is a real behavior choice; it isn’t a neutral default.

A strict C adapter can therefore convert and validate before entering the safe implementation:

use bitflags::bitflags;
bitflags! { #[derive(Clone, Copy)] struct FindFlags: u32 {
  const URLS = 1; const TAGS = 2; const CASE_INSENSITIVE = 4;
} }
fn validate(flags: FindFlags) -> Result<FindFlags, ()> {
  flags.intersects(FindFlags::URLS | FindFlags::TAGS).then_some(flags).ok_or(())
}
fn flags_from_c(raw: u32) -> Result<FindFlags, ()> {
    FindFlags::from_bits(raw).ok_or(()).and_then(validate)
}

The safe search function accepts only FindFlags, so the boundary is the one place that knows C supplied an untyped integer.

Head to the exercise

In exercises/03_intermediate/08_bit_flags, update a set of bookmark output fields using separate enable and disable masks. Detect conflicting changes with typed flags, then strictly convert three raw C masks, apply the update, and return the resulting raw bits.

Exercise

The exercise for this section is located in 03_intermediate/08_bit_flags