Skip to content

← Ground Rules: Values, Types, Control Flow step 4 of 24

Easy Primitives

Numeric literals: suffixes, underscores, other bases

Apply the bit mask 0xFF_00_FF_00 to each input and return the results in input order.

pub fn decode_masks(flags: &[i64]) -> Vec<u32>

For each f, compute f & MASK in i64, then convert that result to u32. Return one result per input; an empty slice produces an empty vector. Inputs may be any i64, including negative values and both endpoints of the range.

Use a constant named MASK with type i64, written as the hexadecimal literal 0xFF_00_FF_00. This exercise practices readable literal notation as well as bitwise operations. Output tests alone cannot enforce the choice of a constant or the spelling of its value.

Which bits survive?

Number bits from zero at the least significant bit. The mask keeps bits 8 through 15 and 24 through 31, clearing all other bits. Each pair of hex digits describes eight bits: FF keeps them and 00 clears them. These are positions in the integer value, independent of the machine's byte order.

Input Result
0 0
4294967295 (0xFFFF_FFFF) 4278255360 (0xFF00_FF00)
-1 4278255360
305419896 (0x1234_5678) 302011904 (0x1200_5600)

Signed integers use two's-complement representation, so -1 has every bit set. The positive i64 mask clears its sign bit and all bits above bit 31. Every masked result is between zero and 4278255360, so the final conversion to u32 preserves its value. Converting the original input to u32 would generally discard high bits; here the mask has already cleared them.

Let context or a suffix choose the type

An unsuffixed integer literal gets its type from context. If inference does not otherwise determine an integer type, it defaults to i32. A type annotation or suffix avoids ambiguity:

fn main() {
    let inferred = 42;
    let small: u8 = 42;
    let wide = 42_i64;
    let count = 1_000_000;
    let bases: [u32; 3] = [0xFF, 0o77, 0b1010_1100];
    let byte = b'A';

    assert_eq!(std::any::type_name_of_val(&inferred), "i32");
    assert_eq!(small, 42u8);
    assert_eq!(wide, 42i64);
    assert_eq!(count, 1000000);
    assert_eq!(bases, [255, 63, 172]);
    assert_eq!(byte, 65u8);
}

The prefixes 0x, 0o and 0b select hexadecimal, octal and binary notation; they do not select the integer type. Underscores in the numeric part separate digits without changing the value. They can also appear before a suffix, as in 42_i64. They are not allowed arbitrarily throughout a token: for example, _42 is an identifier, not the integer literal 42.

A literal must fit its inferred or explicit type. 0xFF_00_FF_00 does not fit in i32, so give the mask its intended i64 type directly.

Declare the mask once

const MASK: i64 = 0xFF_00_FF_00;

This declaration names a value evaluated at compile time and supplies the explicit type required for a constant item. Uppercase names are the usual convention for constants. Grouping the hexadecimal digits by byte makes this mask's pattern easier to inspect than its decimal form.

A constant does not denote one unique storage location. You can borrow a constant's value, but separate uses need not share an address. It is also not a performance guarantee over let: the compiler can optimize ordinary local bindings. See the Rust reference on constants.

The starter's 4278255360 as i64 computes the right value, but Clippy reports unnecessary_cast because the literal can have that type directly. Replace the binding with the typed constant and use its name in the loop. Keep the final as u32: it converts the masked i64 result to the required output type. These two casts serve different purposes.