Character literalLiteral
Explanation
A single-quoted character literal produces a char — a full Unicode
scalar value, always 4 bytes, not a single byte — so '🦀' is just as
valid a char literal as 'H', despite spanning four bytes in UTF-8.
This is a common surprise for newcomers: char in Rust is not "one byte"
the way char is in C — it represents any Unicode scalar value
(excluding surrogate-pair halves), which is why iterating a String
byte-by-byte and iterating it char-by-char (.chars()) can give very
different results for non-ASCII text. See
byte literal for the ASCII-only, single-byte
equivalent (b'H').
Usage examples
Producing a full Unicode char
let grade: char = 'A'; // <- char literal: produces a `char`, a full Unicode scalar value
A char literal must contain exactly one Unicode scalar
value — 'ab' is a compile error, and lone surrogate-pair halves are
never valid scalar values.
Branching on data (pattern matching)
A small hand-written tokenizer matches individual characters directly, including as inclusive range-pattern bounds.
enum Token {
Plus,
Minus,
Number(char),
Whitespace,
}
fn classify(c: char) -> Token {
match c {
'+' => Token::Plus, // <- char literal: matched directly as a pattern
'-' => Token::Minus, // <- char literal: matched directly as a pattern
'0'..='9' => Token::Number(c), // <- char literals as inclusive range-pattern bounds
' ' | '\t' => Token::Whitespace,
_ => panic!("unexpected character {c:?}"),
}
}
Matching char literals directly, including as range
bounds, is exactly the form a small tokenizer needs, and the compiler
enforces exhaustiveness over the match — a guarantee spelled out in the
Reference's pattern grammar.
Working with text
A char is a decoded Unicode scalar value, not a byte — iterating text
char-by-char avoids the panics that raw byte indexing risks on
multi-byte characters.
let word = "café";
// PREFER: iterate by char, not by byte, when the text may contain non-ASCII
let first_char: char = word.chars().next().unwrap(); // <- char, not a byte: always a full Unicode scalar value
assert_eq!(first_char, 'c');
let len_chars = word.chars().count(); // 4 chars
let len_bytes = word.len(); // 5 bytes -- 'é' is 2 bytes in UTF-8
assert_ne!(len_chars, len_bytes);
A char is always a full Unicode scalar value, so
.chars() is the safe way to walk text that might contain multi-byte
characters — a str can't be indexed by a single usize at all, and
slicing it (&s[0..n]) panics if a bound falls inside a multi-byte
character, as the
std str docs
note (use .get() or char-based iteration instead).