Rust Data Types

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, \xNN inside a char or str literal is restricted to 7-bit ASCII values (\x00..=\x7F). For code points above 0x7F, use \u{...} (such as '\u{FF}' for 'ÿ'), or use a byte literal b'\xFF' (which has type u8, not char).

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