---
title: "6.6 Bitwise operators (optional)"
description: "AND, OR, XOR, NOT, and shifts on raw bits, plus the bit-flag idioms used in graphics, embedded, and systems code."
url: "https://learnrust.net/chapter-6/bitwise-operators/"
last_updated: "2026-06-12"
---

# 6.6 Bitwise operators (optional)

> **This lesson is optional**
>
> Bit manipulation is everyday work in graphics, embedded systems, compression, networking, and cryptography, and a curiosity everywhere else. Nothing in the upcoming chapters depends on this lesson; skip it freely and come back when a bitmask shows up in the wild.

Lesson [4.3](https://learnrust.net/chapter-4/isize-usize-and-integer-literals/) called binary literals plus underscores "the classic combination for bit-pattern work" and pointed here. This is the lesson where the bits themselves become the data.

A `u8` is eight bits. Usually you treat the eight as one number; bitwise operators let you treat them as eight tiny switches. To see what we're doing, we need one new formatting verb from the brace mini-language (lesson [5.5](https://learnrust.net/chapter-5/formatting-strings/)'s family): `{:b}` prints a number in binary, and `{:08b}` pads it to eight digits:

```rust
fn main() {
    let x: u8 = 0b0000_0101;
    println!("{x}");
    println!("{x:08b}");
}
```

```
5
00000101
```

Six operators work at this level:

| Operator | Name | Effect |
|---|---|---|
| `x & y` | bitwise AND | 1 where *both* bits are 1 |
| `x \| y` | bitwise OR | 1 where *either* bit is 1 |
| `x ^ y` | bitwise XOR | 1 where the bits *differ* |
| `!x` | bitwise NOT | every bit flipped |
| `x << n` | left shift | bits move `n` places left |
| `x >> n` | right shift | bits move `n` places right |

Two spelling notes for readers arriving from C: Rust's bitwise NOT is `!`, not `~` (the same `!` you know from `bool`s; the operand's type decides which job it does), and `^` is XOR, never exponentiation, which is how lesson [6.2](https://learnrust.net/chapter-6/arithmetic-operators/)'s `2 ^ 10 == 8` mystery happened.

## The four combiners

AND, OR, and XOR all work column by column: line the two numbers up, apply the rule to each pair of bits independently, no carrying, no interaction between columns. NOT is unary and flips everything:

```rust
fn main() {
    let a: u8 = 0b0000_0101;
    let b: u8 = 0b0000_0110;

    println!("a & b = {:08b}", a & b);
    println!("a | b = {:08b}", a | b);
    println!("a ^ b = {:08b}", a ^ b);
    println!("!a    = {:08b}", !a);
}
```

```
a & b = 00000100
a | b = 00000111
a ^ b = 00000011
!a    = 11111010
```

Work the rightmost column of `a & b` by hand: `a` ends in 1, `b` ends in 0, AND demands both, result 0. The column to its left: 0 and 1, still 0. Third column: 1 and 1, finally a 1. Doing this a few times on paper is how it sticks; the quiz will ask.

Now lesson 6.2's payoff. The mysterious `2 ^ 10`:

```text
0000_0010    (2)
0000_1010    (10)
---------    XOR: 1 where they differ
0000_1000    (8)
```

No drama, just an answer to a question nobody meant to ask.

## The shifts

`<<` slides every bit toward the big end, filling with zeros from the right; `>>` slides the other way. Bits pushed off the edge are gone.

```rust
fn main() {
    let x: u8 = 0b0000_1100;

    println!("{:08b}", x << 2);
    println!("{:08b}", x >> 2);
    println!("{:08b}", x << 5);
}
```

```
00110000
00000011
10000000
```

The third line shows the falling-off: shifted by 5, the higher of the two 1-bits ran off the left edge and was lost. Numerically, each left shift doubles a number and each right shift halves it (truncating), which is why compilers love shifts; you'll mostly use them for *placing* bits, as the next section shows.

> **Warning**
>
> Shifting by the type's full width or more (a `u8` by 8, say) is an overflow, with lesson [4.4](https://learnrust.net/chapter-4/integer-overflow/)'s debug-build consequences: a panic ("attempt to shift left with overflow"). And if the compiler can prove it from literals, it refuses to build at all, just like 6.2's division by zero. Keep shift amounts under the bit width.

> **For advanced readers**
>
> On signed integers, `>>` copies the sign bit in from the left instead of zeros (an "arithmetic" shift), preserving negativity at the cost of surprises. This is one of two reasons bit work conventionally uses unsigned types; the other is that `!` and overflow behave more predictably without a sign bit in the way.

All six operators have compound-assignment forms (`&=`, `|=`, `^=`, `<<=`, `>>=`), completing the table lesson [6.3](https://learnrust.net/chapter-6/compound-assignment/) promised.

## Bit flags: eight booleans in a trench coat

Here's the trade that makes all of this practical. A `bool` spends a whole byte storing one yes/no. A `u8` can store *eight* yes/nos, if you're willing to address them with operators instead of names. The idioms:

```rust
const BOLD: u8 = 1 << 0;          // 0000_0001
const ITALIC: u8 = 1 << 1;        // 0000_0010
const UNDERLINE: u8 = 1 << 2;     // 0000_0100
const STRIKETHROUGH: u8 = 1 << 3; // 0000_1000

fn main() {
    let mut style: u8 = 0;

    style |= BOLD;                 // set a flag
    style |= ITALIC | UNDERLINE;   // set several at once
    style &= !UNDERLINE;           // clear a flag
    style ^= STRIKETHROUGH;        // toggle a flag

    println!("style:   {style:08b}");
    println!("bold?    {}", style & BOLD != 0);
    println!("italic?  {}", style & ITALIC != 0);
    println!("under?   {}", style & UNDERLINE != 0);
    println!("strike?  {}", style & STRIKETHROUGH != 0);
}
```

```
style:   00001011
bold?    true
italic?  true
under?   false
strike?  true
```

Each constant is a **bit mask**: a value with exactly one bit set, built with a shift, marking which switch it owns (`1 << 0` is the constant-friendly way to write "bit zero"; lesson [5.1](https://learnrust.net/chapter-5/constants-and-statics/)'s `const` handles the arithmetic at compile time). Then four idioms do everything:

| Intent | Idiom | Why it works |
|---|---|---|
| set | `flags \|= MASK` | OR turns the masked bit on, leaves the rest alone |
| clear | `flags &= !MASK` | NOT makes a mask of everything-*except*, AND keeps only those |
| toggle | `flags ^= MASK` | XOR flips every bit where the mask has a 1, leaves the rest alone |
| test | `flags & MASK != 0` | AND isolates the one bit; any nonzero result means it was set |

(That last idiom works unparenthesized because Rust ranks `&` *above* comparisons in lesson 6.1's table. C ranks it below, a historical accident its own creators regretted, and every C programmer parenthesizes there out of scar tissue.)

This is not an exotic technique. Unix file permissions are nine bit flags (`rwxrwxrwx`). Graphics APIs take flag arguments like `WINDOW_RESIZABLE | WINDOW_FULLSCREEN`. Network protocol headers are bit fields end to end. When you meet those, they'll look exactly like this lesson.

## Quiz time

**Question #1**

Evaluate by hand (all values are `u8`, answers in 8-bit binary):

a) `0b0000_0110 >> 2`
b) `0b0000_0011 | 0b0000_0101`
c) `0b0000_0011 & 0b0000_0101`
d) `(0b0000_0011 | 0b0000_0101) & 0b0000_1001`

<details class="solution">
<summary>Show solution</summary>

a) `00000001` (both bits slide right two places; the low 1 falls off)
b) `00000111` (either)
c) `00000001` (both: only the rightmost column has two 1s)
d) `00000001` (the OR gives `0000_0111`, AND with `0000_1001` keeps only the bottom bit)

</details>

**Question #2**

Using the constants from the lesson, write one line each, then give `style` in binary after all three (starting from `let mut style: u8 = BOLD | ITALIC;`):

a) turn on `STRIKETHROUGH`
b) turn off `BOLD`
c) flip `ITALIC`

<details class="solution">
<summary>Show solution</summary>

```rust
style |= STRIKETHROUGH;
style &= !BOLD;
style ^= ITALIC;
```

Start: `0000_0011`. After (a): `0000_1011`. After (b): `0000_1010`. After (c), the italic bit (currently 1) flips off: `0000_1000`. Only strikethrough survives.

</details>

**Question #3**

Lesson [6.5](https://learnrust.net/chapter-6/logical-operators/)'s De Morgan's laws were stated for `bool`s. Do they hold for bits? Explain why `!(BOLD | ITALIC)` and `!BOLD & !ITALIC` are the same mask.

<details class="solution">
<summary>Show solution</summary>

They hold, column by column. Each bit position is an independent tiny boolean, and bitwise NOT/AND/OR apply the boolean rules to every column at once, so any identity true of `bool`s is true of each column and therefore of the whole value. Concretely: `BOLD | ITALIC` is `0000_0011`, NOT gives `1111_1100`. The other way: `!BOLD` is `1111_1110`, `!ITALIC` is `1111_1101`, and their AND is `1111_1100`. Same mask, both readings: "neither bold nor italic".

</details>

Whether you worked through this lesson or skipped to here: the [chapter summary and quiz](https://learnrust.net/chapter-6/chapter-6-summary-and-quiz/) is next, and it closes out the operators.

## Sitemap

See the full [sitemap](https://learnrust.net/sitemap.md) for all pages.
