223. count_ones — Count Set Bits Without a Loop

Need to count how many bits are set in an integer — flags in a bitmask, a population count, a Hamming weight? Don’t write a shift-and-mask loop. Every integer type has .count_ones(), and it usually lowers to a single CPU instruction.

The hand-rolled version is a loop that masks the low bit and shifts:

1
2
3
4
5
6
7
8
fn count_set_bits(mut n: u32) -> u32 {
    let mut count = 0;
    while n != 0 {
        count += n & 1;
        n >>= 1;
    }
    count
}

It works, but it’s a loop you have to get right, and it’s slower than the hardware can do the same job.

Enter count_ones

1
2
let flags: u32 = 0b1011_0010;
assert_eq!(flags.count_ones(), 4);

One call. It’s available on every integer type (u8..u128, i8..i128), and on most targets it compiles straight to a popcnt instruction.

Where it earns its keep

Hamming distance — XOR two values, then count the bits that differ:

1
2
3
let a: u8 = 0b1100_1010;
let b: u8 = 0b1001_1011;
assert_eq!((a ^ b).count_ones(), 3);

Power-of-two test — a power of two has exactly one bit set:

1
2
3
4
5
6
fn is_power_of_two(n: u32) -> bool {
    n.count_ones() == 1
}

assert!(is_power_of_two(64));
assert!(!is_power_of_two(48));

The rest of the family

count_zeros, leading_zeros, trailing_zeros, leading_ones, and trailing_ones round it out — all single-instruction on modern CPUs. leading_zeros is the trick behind a fast integer log2; trailing_zeros gives you the index of the lowest set bit:

1
2
assert_eq!(0b0010_1000u8.trailing_zeros(), 3); // lowest set bit at index 3
assert_eq!(0b0000_1111u8.count_zeros(), 4);

Next time you reach for a bit-counting loop, reach for count_ones instead. Stable since Rust 1.0.

#222 Jun 2026

222. HashSet::intersection / union / difference — Set Math Without the Manual Loops

Need the items two collections have in common, or the ones only in one of them? Don’t write a for loop with .contains() inside. HashSet has set algebra built in.

The hand-rolled intersection is a loop, a temp Vec, and a membership check you have to get right:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
use std::collections::HashSet;

let a: HashSet<i32> = [1, 2, 3, 4].into_iter().collect();
let b: HashSet<i32> = [3, 4, 5, 6].into_iter().collect();

let mut common = Vec::new();
for x in &a {
    if b.contains(x) {
        common.push(*x);
    }
}
common.sort();
assert_eq!(common, vec![3, 4]);

HashSet gives you the four set operations directly. Each returns a lazy iterator that borrows both sets, so nothing is allocated until you collect:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
use std::collections::HashSet;

let a: HashSet<i32> = [1, 2, 3, 4].into_iter().collect();
let b: HashSet<i32> = [3, 4, 5, 6].into_iter().collect();

// In both
let mut common: Vec<i32> = a.intersection(&b).copied().collect();
common.sort();
assert_eq!(common, vec![3, 4]);

// In either
let mut all: Vec<i32> = a.union(&b).copied().collect();
all.sort();
assert_eq!(all, vec![1, 2, 3, 4, 5, 6]);

// In a, not in b
let mut only_a: Vec<i32> = a.difference(&b).copied().collect();
only_a.sort();
assert_eq!(only_a, vec![1, 2]);

// In exactly one
let mut either: Vec<i32> = a.symmetric_difference(&b).copied().collect();
either.sort();
assert_eq!(either, vec![1, 2, 5, 6]);

The iterators yield &T, which is why .copied() shows up before collect — drop it if you’d rather collect references. (The .sort() calls are only there to make the asserts deterministic; set iteration order isn’t fixed.)

Just asking a yes/no question?

If you only need to know the relationship, not materialize it, three predicates answer in one call without building anything:

1
2
3
4
5
6
7
8
9
use std::collections::HashSet;

let a: HashSet<i32> = [1, 2, 3, 4].into_iter().collect();
let small: HashSet<i32> = [3, 4].into_iter().collect();
let far: HashSet<i32> = [9, 10].into_iter().collect();

assert!(small.is_subset(&a));
assert!(a.is_superset(&small));
assert!(a.is_disjoint(&far));

Whenever you catch yourself looping over one collection to test membership in another, reach for these instead — the intent reads straight off the method name.

#221 Jun 2026

221. from_str_radix — Parse Hex, Binary, or Octal Without Hand-Rolling a Loop

Got a "ff" or a "1010" and need the number behind it? Don’t loop over the characters multiplying by 16. Every integer type has from_str_radix, which parses a string in any base from 2 to 36 in one call.

The hand-rolled version is easy to get subtly wrong — overflow, bad digits, off-by-one on the place value:

1
2
3
4
5
6
7
8
fn parse_hex(s: &str) -> u32 {
    let mut n = 0u32;
    for c in s.chars() {
        n = n * 16 + c.to_digit(16).unwrap();
    }
    n
}
let _ = parse_hex("ff"); // works, but silently overflows on long input

from_str_radix does the whole thing, and returns a Result so bad input is an error instead of a panic or a wrong answer:

1
2
3
4
5
6
7
8
let n = u32::from_str_radix("ff", 16).unwrap();
assert_eq!(n, 255);

let b = u8::from_str_radix("1010", 2).unwrap();
assert_eq!(b, 10);

let o = u16::from_str_radix("755", 8).unwrap();
assert_eq!(o, 493);

It validates digits for you — a character outside the chosen base is a clean Err, not garbage:

1
2
assert!(u32::from_str_radix("xyz", 16).is_err());
assert!(u8::from_str_radix("2", 2).is_err()); // '2' isn't a binary digit

A real use: cracking a #RRGGBB color into channels. Slice, parse, done — no manual nibble math:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
fn rgb(hex: &str) -> Option<(u8, u8, u8)> {
    let h = hex.strip_prefix('#').unwrap_or(hex);
    if h.len() != 6 { return None; }
    let r = u8::from_str_radix(&h[0..2], 16).ok()?;
    let g = u8::from_str_radix(&h[2..4], 16).ok()?;
    let b = u8::from_str_radix(&h[4..6], 16).ok()?;
    Some((r, g, b))
}

assert_eq!(rgb("#E8593C"), Some((232, 89, 60)));
assert_eq!(rgb("oops"), None);

Signed types work too, and a leading - is honored:

1
assert_eq!(i32::from_str_radix("-2a", 16), Ok(-42));

For plain base-10 you don’t even need it — "42".parse::<u32>() is the same thing with the radix fixed at 10. Reach for from_str_radix the moment the base isn’t ten.

#220 Jun 2026

220. ilog10 — Count an Integer's Digits Without Formatting It to a String

Need to know how many digits a number has? Reaching for n.to_string().len() allocates a whole String just to measure it. ilog10 answers the same question with one instruction and zero allocation.

The classic way to count digits builds a string and throws it away:

1
2
3
let n: u32 = 4096;
let digits = n.to_string().len(); // heap-allocates a String to count 4 chars
assert_eq!(digits, 4);

ilog10 returns the floor of the base-10 logarithm, so the digit count is just that plus one — no allocation, no formatting:

1
2
3
let n: u32 = 4096;
let digits = n.ilog10() + 1;
assert_eq!(digits, 4);

The one catch: 0 has no logarithm, so 0u32.ilog10() panics. Guard the zero case, since “0” still has one digit:

1
2
3
4
5
6
7
fn digit_count(n: u32) -> u32 {
    if n == 0 { 1 } else { n.ilog10() + 1 }
}

assert_eq!(digit_count(0), 1);
assert_eq!(digit_count(7), 1);
assert_eq!(digit_count(1_000_000), 7);

Prefer no branch? checked_ilog10 returns None for zero instead of panicking, so you can fold the special case into one expression:

1
2
3
let count = |n: u32| n.checked_ilog10().map_or(1, |d| d + 1);
assert_eq!(count(0), 1);
assert_eq!(count(99), 2);

There’s also ilog2 when you want a power-of-two magnitude — the index of the highest set bit:

1
2
assert_eq!(255u32.ilog2(), 7); // 0b1111_1111, top bit is bit 7
assert_eq!(256u32.ilog2(), 8);

All three (ilog10, ilog2, checked_ilog10) work on every integer type. Skip the string round-trip — the math is right there.

#219 Jun 2026

219. checked_add_signed — Move an Unsigned Index by a Signed Delta, No Cast

You have a usize index and a delta: isize that might be negative. idx + delta won’t even compile, and casting your way around it wraps silently on underflow.

The naive fixes are both wrong in their own way:

1
2
3
4
5
let idx: usize = 3;
let delta: isize = -2;

// let next = idx + delta;          // error: cannot add `isize` to `usize`
let next = (idx as isize + delta) as usize; // compiles, wraps on underflow

That cast dance hides bugs: subtract past zero and you get a gigantic index instead of an error.

checked_add_signed adds a signed offset to an unsigned integer and hands back an OptionNone exactly when the result would underflow below zero or overflow the type:

1
2
3
4
5
let idx: usize = 3;

assert_eq!(idx.checked_add_signed(2),  Some(5));
assert_eq!(idx.checked_add_signed(-2), Some(1));
assert_eq!(idx.checked_add_signed(-4), None);   // would go below 0

So moving a cursor inside bounds becomes one honest expression — no as, no manual if delta < 0 branch:

1
2
3
4
5
6
7
fn move_cursor(pos: usize, delta: isize, len: usize) -> Option<usize> {
    pos.checked_add_signed(delta).filter(|&p| p < len)
}

assert_eq!(move_cursor(2, 1, 5), Some(3));
assert_eq!(move_cursor(0, -1, 5), None); // off the front
assert_eq!(move_cursor(4, 1, 5), None);  // off the back

It’s available on every unsigned type with its matching signed offset (u32 takes i32, usize takes isize, and so on). If you’d rather clamp than reject, the saturating_add_signed sibling pins the result to the type’s bounds instead of returning None. And as of Rust 1.90 the _sub_signed variants round out the set for subtracting a signed amount.

#218 Jun 2026

218. str::match_indices — Find Every Match and Its Position in One Pass

Hand-rolling a find loop to locate every occurrence of a substring means juggling a running offset and remembering to skip past each match. match_indices hands you each hit and its byte position as an iterator — no bookkeeping.

The classic way to collect every position of a needle is a loop over find, slicing the remainder each time and adding the offset back by hand:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
let log = "ERROR x ERROR y WARN z ERROR";

let mut positions = Vec::new();
let mut start = 0;
while let Some(i) = log[start..].find("ERROR") {
    let idx = start + i;
    positions.push(idx);
    start = idx + "ERROR".len(); // easy to get this advance wrong
}

assert_eq!(positions, vec![0, 8, 23]);

It works, but the offset arithmetic is exactly the kind of thing you fumble at 5pm. match_indices walks the string for you and yields (byte_index, matched_str) pairs:

1
2
3
4
5
let log = "ERROR x ERROR y WARN z ERROR";

let positions: Vec<(usize, &str)> = log.match_indices("ERROR").collect();

assert_eq!(positions, vec![(0, "ERROR"), (8, "ERROR"), (23, "ERROR")]);

It’s lazy and composes like any other iterator, so you can map down to just the indices, or count without allocating at all:

1
2
3
4
let log = "ERROR x ERROR y WARN z ERROR";

let count = log.matches("ERROR").count();
assert_eq!(count, 3);

The pattern argument is the full Pattern family — a &str, a char, a closure, or a slice of chars — so you can find runs of a class of characters without a regex:

1
2
3
4
let s = "a1b22c333";

let digits: Vec<(usize, &str)> = s.match_indices(char::is_numeric).collect();
assert_eq!(digits, vec![(1, "1"), (3, "2"), (4, "2"), (6, "3"), (7, "3"), (8, "3")]);

Matches are non-overlapping and scanned left to right; when you need the last hit first, rmatch_indices walks right to left:

1
2
3
4
let s = "aXbXc";

let last = s.rmatch_indices('X').next();
assert_eq!(last, Some((3, "X")));

The byte indices are real &str offsets, so they slot straight into slicing and replacement logic without any conversion. When you only care about whether something matches, reach for contains; when you want each location, match_indices already did the bookkeeping.

#217 Jun 2026

217. eq_ignore_ascii_case — Case-Insensitive Compare Without the Allocation

a.to_lowercase() == b.to_lowercase() allocates two fresh Strings just to throw them away after the comparison. For ASCII text — headers, extensions, keywords — eq_ignore_ascii_case does it byte-by-byte with zero allocations.

The reflexive way to compare strings case-insensitively is to lowercase both sides and check for equality:

1
2
3
4
5
let header = "Content-Type";
let wanted = "content-type";

// Two heap allocations, then immediately dropped
assert!(header.to_lowercase() == wanted.to_lowercase());

That’s two Strings built on the heap for a question that’s really just “are these equal if we ignore case?” The standard library answers it directly, walking both byte sequences and folding AZ against az as it goes — no allocation, and it bails early on the first mismatch:

1
2
3
let header = "Content-Type";

assert!(header.eq_ignore_ascii_case("content-type"));

It’s defined on [u8] too, which is handy when you’re matching protocol tokens straight out of a buffer without first validating UTF-8:

1
assert!(b"GET".eq_ignore_ascii_case(b"get"));

The one thing to know: it folds only ASCII letters. Non-ASCII bytes must match exactly, so it won’t treat Ä and ä as equal, and it won’t expand ß:

1
2
assert!(!"Ä".eq_ignore_ascii_case("ä"));
assert!(!"Straße".eq_ignore_ascii_case("STRASSE"));

For human-facing, multilingual text you still want full Unicode case folding. But for the things you actually compare case-insensitively in systems code — HTTP methods and header names, file extensions, config keys, command names — the input is ASCII by definition, and reaching for eq_ignore_ascii_case is both faster and clearer:

1
2
3
4
5
6
7
fn is_jpeg(ext: &str) -> bool {
    ext.eq_ignore_ascii_case("jpg") || ext.eq_ignore_ascii_case("jpeg")
}

assert!(is_jpeg("JPG"));
assert!(is_jpeg("Jpeg"));
assert!(!is_jpeg("png"));

If you need to fold case in place rather than compare, make_ascii_lowercase mutates a &mut str or &mut [u8] without allocating either — same ASCII-only rule applies.

#216 Jun 2026

216. unsigned_abs — i32::MIN Has No Positive Twin, So .abs() Overflows

(-5i32).abs() is fine, but i32::MIN.abs() panics — the magnitude 2147483648 doesn’t fit back into an i32. unsigned_abs returns the magnitude in the unsigned type, where it always fits.

Two’s complement isn’t symmetric. An i32 reaches down to -2147483648 but only up to 2147483647, so the most-negative value has no positive counterpart. That makes abs a landmine on exactly one input:

1
2
3
4
5
let x: i32 = -5;
assert_eq!(x.abs(), 5); // fine

let edge = i32::MIN;
let m = edge.abs(); // panics in debug, wraps to i32::MIN in release

The same trap hides inside the classic (a - b).abs() distance trick, which is why abs_diff exists for differences. But when you have a single signed value and just want its magnitude, the fix is unsigned_abs:

1
2
3
let edge = i32::MIN;

assert_eq!(edge.unsigned_abs(), 2147483648u32);

It returns the unsigned sibling type (i32u32, i8u8, …), which is wide enough to hold the magnitude of every input — including the awkward one — so it can never overflow:

1
2
3
assert_eq!((-5i32).unsigned_abs(), 5u32);
assert_eq!((127i8).unsigned_abs(), 127u8);
assert_eq!(i8::MIN.unsigned_abs(), 128u8); // .abs() would panic here

If you genuinely need a signed result and want to handle the edge instead of ignoring it, checked_abs hands you an Option and saturating_abs clamps to MAX:

1
2
assert_eq!(i32::MIN.checked_abs(), None);
assert_eq!(i32::MIN.saturating_abs(), i32::MAX);

Any time you take the absolute value of a signed integer that could conceivably be MIN — parsed input, a subtraction result, a delta from untrusted data — reach for unsigned_abs instead of abs and the panic simply can’t happen.

#215 Jun 2026

215. next_multiple_of — Round Up to a Multiple Without the +m-1 Dance

Padding a length up to the next multiple of 8? The classic (n + 7) / 8 * 8 works right up until it overflows or you fat-finger the constant. next_multiple_of says exactly what you mean.

The hand-rolled version shows up everywhere alignment matters — buffer sizes, page rounding, table padding:

1
2
3
4
// Easy to get subtly wrong, and `n + m - 1` can overflow near the top of the range
fn round_up(n: usize, m: usize) -> usize {
    (n + m - 1) / m * m
}

Every integer type has next_multiple_of: the smallest value >= self that is a multiple of the argument. Already a multiple? It’s returned unchanged.

1
2
3
4
assert_eq!(13u32.next_multiple_of(8), 16); // round up
assert_eq!(16u32.next_multiple_of(8), 16); // already aligned, untouched
assert_eq!(0u32.next_multiple_of(8), 0);
assert_eq!(23u32.next_multiple_of(10), 30);

When the rounded value would overflow, next_multiple_of panics — but checked_next_multiple_of hands you an Option instead, so you stay in control:

1
2
assert_eq!(250u8.checked_next_multiple_of(8), None); // 256 won't fit in u8
assert_eq!(13u8.checked_next_multiple_of(8), Some(16));

One method, the intent on the page, and no off-by-one waiting to bite you.

#214 Jun 2026

214. slice::windows — Compare Consecutive Elements Without the Index Math

The classic “look at each element and the next one” loop is a panic waiting to happen: 0..v.len() - 1 underflows the moment the slice is empty. windows hands you every consecutive pair safely, no arithmetic required.

Whenever you need to look at neighbouring elements — deltas between samples, “is each one bigger than the last”, spotting a transition — the reflex is to index by hand:

1
2
3
4
5
6
let v = [3, 7, 2, 9];

for i in 0..v.len() - 1 {
    let diff = v[i + 1] - v[i];
    // ...
}

This works until v is empty. v.len() is 0, v.len() - 1 underflows on usize, and the loop panics before it even starts. You end up bolting on an if !v.is_empty() guard, and the v[i + 1] indexing still pays a bounds check every iteration.

slice::windows(n) gives you an iterator of overlapping sub-slices of length n. Ask for 2 and you get each consecutive pair, with no index juggling and no empty-slice trap:

1
2
3
4
5
6
7
let v = [3, 7, 2, 9];

let diffs: Vec<i32> = v.windows(2)
    .map(|w| w[1] - w[0])
    .collect();

assert_eq!(diffs, vec![4, -5, 7]);

Because windows yields nothing when the slice is shorter than n, the empty and single-element cases just fall out for free — no special handling:

1
2
3
4
5
let empty: [i32; 0] = [];
assert_eq!(empty.windows(2).count(), 0);

let one = [42];
assert_eq!(one.windows(2).count(), 0);

It composes with the rest of the iterator toolbox, too. Checking that a slice is strictly increasing becomes one readable line:

1
2
3
let v = [1, 4, 6, 10];
let strictly_increasing = v.windows(2).all(|w| w[0] < w[1]);
assert!(strictly_increasing);

The window size isn’t limited to 2 — pass 3 for sliding triples (handy for smoothing or peak detection), and each window is a real slice you can index, sum, or pattern-match:

1
2
3
let v = [1, 2, 3, 4, 5];
let sums: Vec<i32> = v.windows(3).map(|w| w.iter().sum()).collect();
assert_eq!(sums, vec![6, 9, 12]);

One caveat: the windows overlap and share elements, so windows only hands out shared (&) references — you can’t mutate through them. When you want non-overlapping fixed-size groups instead, reach for chunks. But for anything that compares an element to its neighbours, windows replaces the fragile len() - 1 loop with code that can’t go out of bounds.