Skip to content

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

Easy Primitives

bool and char

Classify each Unicode scalar value in a string, preserving its order.

pub fn char_kinds(text: &str) -> Vec<String>

Iterate with text.chars() and apply these checks in order:

First matching check Output label
is_ascii_digit() "digit"
is_alphabetic() "alpha"
is_whitespace() "space"
None of the above "other"

Return one owned String per scalar value. Empty input produces an empty vector. For example, "a1 é" gives ["alpha", "digit", "space", "alpha"].

Bytes, scalar values and visible characters

A Rust char holds one Unicode scalar value and occupies four bytes. Scalar values range from U+0000 to U+10FFFF, excluding the surrogate range U+D800..=U+DFFF used in UTF-16 encoding. A str instead stores UTF-8, where each scalar value uses one to four bytes:

fn main() {
    let text = "a1 é";
    assert_eq!(text.len(), 5);          // UTF-8 bytes
    assert_eq!(text.chars().count(), 4);
    assert_eq!(size_of::<char>(), 4);
    assert_eq!('é'.len_utf8(), 2);
    assert_eq!('語'.len_utf8(), 3);
    assert_eq!('🦀'.len_utf8(), 4);
}

.bytes() would visit the two UTF-8 bytes of é separately. This task classifies the decoded scalar value, so use .chars().

A scalar value is not always a whole visible character. A letter and a combining accent can display together while remaining two scalar values. Some emoji sequences also contain several. This exercise does not group grapheme clusters or normalize different spellings of the same visible text:

fn main() {
    let composed = "é";
    let decomposed = "e\u{301}";
    assert_eq!(composed.chars().count(), 1);
    assert_eq!(decomposed.chars().count(), 2);
    assert_eq!(decomposed.chars().collect::<Vec<_>>(), ['e', '\u{301}']);
}

The first string produces ["alpha"]; the second produces ["alpha", "other"]. That specific combining accent is not alphabetic; do not assume every combining mark has the same Unicode properties.

Use the specified predicates

is_ascii_digit accepts only 0 through 9. is_numeric is broader and would change the task: Arabic-Indic ٣ is numeric but receives "other" here. Roman numeral Ⅳ is both numeric and alphabetic, so it receives "alpha" under these rules.

is_alphabetic uses Unicode's Alphabetic property, which includes more than ASCII letters. is_whitespace uses its White_Space property, covering tabs, newlines and non-breaking space. A zero-width space (U+200B) does not satisfy that predicate and receives "other".

Conditions are already booleans

Each predicate returns a bool. Use it directly in an if condition; writing == true is redundant. Rust requires a boolean condition, so if 1 is a type error (E0308). For a numeric condition, write the comparison you intend, such as count != 0.

Explicit boolean-to-integer conversion is allowed when needed:

fn main() {
    let has_digit = '7'.is_ascii_digit();
    let label = if has_digit { "digit" } else { "other" };
    assert_eq!(label, "digit");
    assert_eq!(u8::from(has_digit), 1);
    assert_eq!(u8::from(false), 0);
}

The starter triggers Clippy's default bool_comparison warning and omits the whitespace branch. Fix both. Removing == true simplifies the source; it is not a claim that the original emits an extra machine instruction.

Related default Clippy warnings include needless_bool, for expressions such as if condition { true } else { false }, and char_lit_as_u8, for character literals cast to bytes. For an ASCII byte, write b'A' directly. Casting a non-ASCII char to u8 is not UTF-8 encoding.

Build each label with an if/else if chain returning &str, then call to_string() when adding it to the output. No formatting is needed. Submissions must compile, pass the tests and pass Clippy with warnings treated as errors.