Bitwise
Underneath, every integer is a row of bits. Arithmetic treats that row as one number; the bitwise operators treat it as a row of separate yes-or-no switches and work on each position on its own. That makes them the tool for packing several small facts into one value — eight permissions in a single byte, a set of options in one argument — and for reading them back out.
Four operators, one bit at a time
| Operator | Name | A result bit is 1 when… |
|---|---|---|
a & b | AND | both bits are 1 |
a | b | OR | either bit is 1 |
a ^ b | XOR | exactly one of the two bits is 1 |
~a | NOT | the bit in a is 0 — every bit flips |
The program prints them side by side. {:08b} formats a number in binary, padded with zeros to eight digits, so the columns line up:
let a: uint8 = 0b1100_1010;
let b: uint8 = 0b1010_0110;
PrintLine("a {:08b}", a);
PrintLine("b {:08b}", b);
PrintLine("a & b {:08b}", a & b);
PrintLine("a | b {:08b}", a | b);
PrintLine("a ^ b {:08b}", a ^ b);
PrintLine("~a {:08b}", ~a);
a 11001010
b 10100110
a & b 10000010
a | b 11101110
a ^ b 01101100
~a 00110101
Read any column top to bottom and the rule of the table holds. ~ depends on the width of its operand: ~ of a uint8 flips eight bits, ~ of a uint32 flips thirty-two. Unsigned types are the usual choice for bit work, because no bit doubles as a sign.
Flags and masks
A mask is a value whose set bits pick out the positions you care about. Give each flag its own bit, and one byte holds eight of them. Written in binary, the constants show which bit each one owns:
const Read: uint8 = 0b001;
const Write: uint8 = 0b010;
const Execute: uint8 = 0b100;
Four idioms cover almost everything done with flags:
| Goal | Idiom | Why it works |
|---|---|---|
| Set a flag | flags |= Write | OR turns that bit on and leaves the rest alone |
| Clear a flag | flags &= ~Read | ~Read has every bit but one set; AND keeps those |
| Flip a flag | flags ^= Execute | XOR with 1 flips a bit, XOR with 0 keeps it |
| Test a flag | (flags & Write) != 0 | AND keeps only that bit; non-zero means it was set |
In the program they run one after another on flags:
var flags: uint8 = Read;
flags |= Write;
flags ^= Execute;
flags &= ~Read;
The byte goes 001 → 011 → 111 → 110: it started with Read, gained Write, gained Execute by a flip, and lost Read.
flowchart LR
s["001<br/>Read"] -- "set Write" --> w["011"]
w -- "flip Execute" --> x["111"]
x -- "clear Read" --> r["110<br/>Write, Execute"]Testing flags
Testing asks whether a bit survives the mask:
PrintLine("can write? {}", (flags & Write) != 0);
PrintLine("can read? {}", (flags & Read) != 0);
A mask with several bits tests them together. != 0 would mean "any of them"; comparing with the mask itself means "all of them":
let both = Write | Execute;
PrintLine("write and run? {}", (flags & both) == both);
Watch the precedence
&, | and ^ bind more loosely than == and !=. So flags & Write == 0 reads as flags & (Write == 0) — a byte AND a boolean — and the compiler rejects it. Parenthesise the mask every time: (flags & Write) == 0.
The program
The whole lesson is one package in the Examples repository. Its comments explain every step.
// The bitwise operators treat an integer as a row of bits and work on each position separately.
// `a & b` keeps a bit only where both have it, `a | b` where either has it, and `a ^ b` where
// exactly one has it. `~a` flips every bit, so its result depends on the width: `~` of a `uint8`
// flips eight bits, of a `uint32` thirty-two.
//
// Their everyday use is the mask: a value whose set bits pick out the positions you care about.
// One byte can then hold eight yes-or-no flags, and four idioms cover almost everything done with
// them: `|` sets a flag, `& ~` clears it, `^` flips it, and `&` tests it.
//
// Watch the precedence. `&`, `|` and `^` bind more loosely than `==`, so `flags & Write == 0` reads
// as `flags & (Write == 0)` and is rejected for mixing a `uint8` with a `bool`. Parenthesize the
// mask: `(flags & Write) == 0`.
import Io::PrintLine;
// One bit per permission. Written in binary, the masks show which bit each one owns.
const Read: uint8 = 0b001;
const Write: uint8 = 0b010;
const Execute: uint8 = 0b100;
func Main() -> int {
// The four operators, side by side.
let a: uint8 = 0b1100_1010;
let b: uint8 = 0b1010_0110;
PrintLine("a {:08b}", a);
PrintLine("b {:08b}", b);
PrintLine("a & b {:08b}", a & b);
PrintLine("a | b {:08b}", a | b);
PrintLine("a ^ b {:08b}", a ^ b);
PrintLine("~a {:08b}", ~a);
PrintLine("");
// Masks at work on a set of flags.
var flags: uint8 = Read;
PrintLine("start {:03b}", flags);
flags |= Write;
PrintLine("set Write {:03b}", flags);
flags ^= Execute;
PrintLine("flip Execute {:03b}", flags);
flags &= ~Read;
PrintLine("clear Read {:03b}", flags);
PrintLine("can write? {}", (flags & Write) != 0);
PrintLine("can read? {}", (flags & Read) != 0);
// A mask with several bits tests them together.
let both = Write | Execute;
PrintLine("write and run? {}", (flags & both) == both);
return 0;
}
Run it
cd Examples/Numbers/Bitwise
rux run
a 11001010
b 10100110
a & b 10000010
a | b 11101110
a ^ b 01101100
~a 00110101
start 001
set Write 011
flip Execute 111
clear Read 110
can write? true
can read? false
write and run? true
Common mistakes
flags & Write == 0 groups as flags & (Write == 0) and fails with error: operator '&' cannot combine left operand 'uint8' with right operand 'bool8'. Write (flags & Write) == 0.! to invert a mask.! is logical NOT, for booleans only: flags & !Write fails with error: operator '!' requires a bool operand, but found 'uint8'. The bitwise NOT is ~: flags & ~Write.Rux does not treat a non-zero number as true.
if flags & Write { … } fails with error: condition for 'if' must have type 'bool', but found 'uint8'. Compare it: if (flags & Write) != 0 { … }.Try it yourself
- Add a fourth flag,
Delete: uint8 = 0b1000, set it, and printflagswith{:04b}. - Write
func Has(flags: uint8, mask: uint8) -> boolthat returns whether every bit ofmaskis set inflags. - XOR a value with the same mask twice. What do you get back, and why?
- Change
aandbtouint32and print~awith{:032b}. How many bits flipped this time?
Learn more
- Bitwise operations in the Rux Reference
- Shift — moving bits along, and building masks with
1 << n - Bit operation — counting and rotating bits
- Format number —
{:b},{:x}and padding