Character Type
Rust's char type represents a single Unicode Scalar Value. Unlike a C or C++ char (which is a single 1-byte integer representing an ASCII character or raw UTF-8 byte), a Rust char is always 4 bytes (32 bits) in size and can hold any Unicode character—from ASCII letters and digits to accented scripts, CJK ideographs, mathematical symbols, and emoji.
char Literals and Escape Sequences
A char literal is written between single quotes ('a'), whereas string literals use double quotes ("a"):
fn main() {
let ascii_letter: char = 'R';
let digit: char = '7';
let bangla_letter: char = 'ক';
let greek_pi: char = 'π';
let crab_emoji: char = '🦀';
println!("{ascii_letter} {digit} {bangla_letter} {greek_pi} {crab_emoji}");
}
R 7 ক π 🦀
Special characters and arbitrary Unicode code points can be written using backslash escape sequences inside single quotes:
| Escape Syntax | Example | Code Point | Meaning |
|---|---|---|---|
'\n' |
'\n' |
U+000A |
Line feed (newline) |
'\r' |
'\r' |
U+000D |
Carriage return |
'\t' |
'\t' |
U+0009 |
Horizontal tab |
'\\' |
'\\' |
U+005C |
Literal backslash (\) |
'\'' |
'\'' |
U+0027 |
Literal single quote (') |
'\0' |
'\0' |
U+0000 |
Null character (NUL) |
'\xNN' |
'\x41' |
U+0041 ('A') |
7-bit ASCII hex escape (\x00 through \x7F only!) |
'\u{H...}' |
'\u{1F980}' |
U+1F980 ('🦀') |
1 to 6 hex digits specifying any valid Unicode scalar value |
fn main() {
let hex_a = '\x41';
let heart = '\u{2764}';
let crab = '\u{1F980}';
println!("hex_a = {hex_a}, heart = {heart}, crab = {crab}");
}
hex_a = A, heart = ❤, crab = 🦀
Note: In Rust,
\xNNinside acharorstrliteral is restricted to 7-bit ASCII values (\x00..=\x7F). For code points above0x7F, use\u{...}(such as'\u{FF}'for'ÿ'), or use a byte literalb'\xFF'(which has typeu8, notchar).
Memory Representation and Valid Unicode Scalar Ranges
Every char occupies 4 bytes (32 bits) in memory, storing the character's Unicode code point directly as a 32-bit integer (equivalent to UTF-32):
char in memory (always 4 bytes / 32 bits):
'A' (U+0041) --> [ 0x0000_0041 ]
'π' (U+03C0) --> [ 0x0000_03C0 ]
'🦀' (U+1F980) --> [ 0x0001_F980 ]
However, not every 32-bit integer is a valid char. A valid Unicode Scalar Value must fall into one of two disjoint ranges, excluding the UTF-16 surrogate code point block (0xD800..=0xDFFF) and anything above 0x10FFFF:
0x0000 ────────────── 0xD7FF 0xD800 ────── 0xDFFF 0xE000 ────────────── 0x10FFFF 0x110000 ── 0xFFFFFFFF
[ Valid Scalar Range 1 ] [ UTF-16 Surrogates ] [ Valid Scalar Range 2 ] [ Above Unicode ]
(Allowed) (FORBIDDEN) (Allowed) (FORBIDDEN)
Because bit patterns above 0x10FFFF (and 0xD800..=0xDFFF) are never valid for char, the compiler uses those unused bit patterns for niche optimization: Option<char> is also 4 bytes in size!
use std::mem::size_of;
fn main() {
println!("size_of::<char>() = {} bytes", size_of::<char>());
println!("size_of::<Option<char>>() = {} bytes", size_of::<Option<char>>());
println!("char::MAX code point = U+{:X}", char::MAX as u32);
}
size_of::<char>() = 4 bytes
size_of::<Option<char>>() = 4 bytes
char::MAX code point = U+10FFFF
Converting Between char and Integers
| Conversion Direction | Allowed Types & Methods | Why |
|---|---|---|
char -> u32 (or u64, u128, usize) |
u32::from(c) or c as u32 |
Always succeeds; every char is a valid 32-bit code point |
u8 -> char |
char::from(byte) or byte as char |
Always succeeds (u8 is the only integer type that can be cast to char with as!), mapping 0..=255 to U+0000..=U+00FF |
u32 -> Option<char> |
char::from_u32(code) |
Fallible; returns None if code is a surrogate (0xD800..=0xDFFF) or > 0x10FFFF |
char <-> Digit (0..=9, hex 0..=F) |
c.to_digit(radix) / char::from_digit(n, radix) |
Converts between a character like '7' and numeric value 7 (unlike as u32, which returns ASCII code 55!) |
fn main() {
let ch = '7';
// 1. Unicode code point value (U+0037 == 55)
let code_point = u32::from(ch);
println!("Code point of '{ch}': {code_point} (0x{code_point:X})");
// 2. Numeric digit value ('7' -> Some(7))
let numeric_digit = ch.to_digit(10);
let hex_digit = 'f'.to_digit(16);
println!("to_digit(10) of '7': {numeric_digit:?}, to_digit(16) of 'f': {hex_digit:?}");
// 3. Checked conversion from u32 code point to char
println!("from_u32(0x1F980): {:?}", char::from_u32(0x1F980));
println!("from_u32(0xD800): {:?} (invalid surrogate)", char::from_u32(0xD800));
}
Code point of '7': 55 (0x37)
to_digit(10) of '7': Some(7), to_digit(16) of 'f': Some(15)
from_u32(0x1F980): Some('🦀')
from_u32(0xD800): None (invalid surrogate)
Built-In char Classification and Case Methods
The char type includes methods for inspecting both Unicode properties and fast 7-bit ASCII properties:
| Method | Example ('a', '9', ' ', 'Δ') |
Description |
|---|---|---|
c.is_alphabetic() |
'a' -> true, 'Δ' -> true |
True for any Unicode alphabetic letter |
c.is_numeric() |
'9' -> true, '¾' -> true |
True for any Unicode numeric character |
c.is_alphanumeric() |
'a' -> true, '9' -> true |
True if alphabetic or numeric |
c.is_whitespace() |
' ' -> true, '\n' -> true |
True for Unicode whitespace |
c.is_ascii() |
'a' -> true, 'Δ' -> false |
True if code point is in 0x00..=0x7F |
c.is_ascii_digit() |
'9' -> true |
Fast check for '0'..='9' |
c.to_ascii_uppercase() |
'a' -> 'A' |
Returns a single char uppercased for ASCII |
c.to_uppercase() |
'ß'.to_uppercase() -> "SS" |
Returns an iterator of chars (because one Unicode char can uppercase into multiple chars!) |
c.len_utf8() |
'A' -> 1, '🦀' -> 4 |
Number of bytes (1 to 4) needed to encode c in UTF-8 |
fn main() {
let ch = '🦀';
println!("'{ch}' takes {} bytes in UTF-8, but {} bytes as a `char` in memory.", ch.len_utf8(), std::mem::size_of_val(&ch));
// Unicode uppercase can expand 1 char into 2 chars (German Eszett 'ß' -> "SS")
let upper: String = 'ß'.to_uppercase().collect();
println!("'ß' uppercased: {upper}");
}
'🦀' takes 4 bytes in UTF-8, but 4 bytes as a `char` in memory.
'ß' uppercased: SS
Common Errors
Multi-Character or Grapheme Cluster in Single Quotes
A char literal must contain exactly one Unicode scalar value. Putting multiple characters (or a multi-scalar emoji sequence such as a flag or skin-tone modifier) inside single quotes fails to compile:
// ❌ does not compile
fn main() {
let invalid = 'ab';
}
error: character literal may only contain one codepoint
--> src/main.rs:2:19
|
2 | let invalid = 'ab';
| ^^^^
|
help: if you meant to write a string literal, use double quotes
|
2 - let invalid = 'ab';
2 + let invalid = "ab";
|
error: aborting due to 1 previous error