Rust - Booleans & Characters
Overview
Estimated time: 30β40 minutes
Master Rust's boolean type for logical operations and the char type for Unicode character handling, including character manipulation and validation methods.
Learning Objectives
- Understand boolean operations and short-circuiting
- Master character literals and Unicode handling
- Learn character classification and manipulation methods
- Practice with logical expressions and character processing
Prerequisites
Boolean Type
Basic Boolean Operations
fn main() {
let is_true = true;
let is_false = false;
// Logical AND
println!("true && false = {}", is_true && is_false);
// Logical OR
println!("true || false = {}", is_true || is_false);
// Logical NOT
println!("!true = {}", !is_true);
println!("!false = {}", !is_false);
// Comparison results are booleans
let x = 5;
let y = 10;
println!("{} == {} is {}", x, y, x == y);
println!("{} < {} is {}", x, y, x < y);
println!("{} >= {} is {}", x, y, x >= y);
}
Output:
true && false = false
true || false = true
!true = false
!false = true
5 == 10 is false
5 < 10 is true
5 >= 10 is false
Short-Circuit Evaluation
fn expensive_operation() -> bool {
println!("Expensive operation called!");
true
}
fn main() {
// Short-circuit AND: second expression not evaluated if first is false
let result1 = false && expensive_operation();
println!("Result 1: {}", result1);
// Short-circuit OR: second expression not evaluated if first is true
let result2 = true || expensive_operation();
println!("Result 2: {}", result2);
// Both expressions evaluated when needed
let result3 = true && expensive_operation();
println!("Result 3: {}", result3);
}
Output:
Result 1: false
Result 2: true
Expensive operation called!
Result 3: true
Boolean in Control Flow
fn main() {
let age = 18;
let has_license = true;
let has_car = false;
let can_drive = age >= 16 && has_license;
let can_drive_alone = can_drive && (age >= 18 || has_car);
if can_drive_alone {
println!("You can drive alone!");
} else if can_drive {
println!("You can drive with supervision.");
} else {
println!("You cannot drive yet.");
}
// Boolean to string
println!("Can drive: {}", can_drive);
println!("Can drive alone: {}", can_drive_alone);
}
Output:
You can drive alone!
Can drive: true
Can drive alone: true
Character Type
Character Literals
fn main() {
// Basic ASCII characters
let letter = 'A';
let digit = '5';
let symbol = '$';
// Unicode characters
let emoji = 'π';
let chinese = 'δΈ';
let math = 'Ο';
println!("ASCII: {}, {}, {}", letter, digit, symbol);
println!("Unicode: {}, {}, {}", emoji, chinese, math);
// Escape sequences
let newline = '\n';
let tab = '\t';
let quote = '\'';
let backslash = '\\';
println!("Escaped characters work in strings too:");
println!("Quote: {}, Tab:{} Backslash: {}", quote, tab, backslash);
}
Output:
ASCII: A, 5, $
Unicode: π, δΈ, Ο
Escaped characters work in strings too:
Quote: ', Tab: Backslash: \
Unicode Code Points
fn main() {
let char_a = 'A';
let char_unicode = '\u{1F600}'; // π emoji
let char_hex = '\x41'; // 'A' in hex
println!("Character: {}", char_a);
println!("Unicode emoji: {}", char_unicode);
println!("Hex character: {}", char_hex);
// Get numeric value of character
let code_point = char_a as u32;
println!("'A' as u32: {}", code_point);
// Create character from code point
if let Some(ch) = char::from_u32(65) {
println!("char from 65: {}", ch);
}
// Invalid code point
if let Some(ch) = char::from_u32(0x1FFFFF) {
println!("Valid: {}", ch);
} else {
println!("Invalid Unicode code point");
}
}
Output:
Character: A
Unicode emoji: π
Hex character: A
'A' as u32: 65
char from 65: A
Invalid Unicode code point
Character Methods
Character Classification
fn main() {
let chars = ['A', 'z', '5', ' ', '!', 'δΈ', 'π'];
for ch in chars {
println!("\nCharacter: '{}'", ch);
println!(" is_alphabetic: {}", ch.is_alphabetic());
println!(" is_numeric: {}", ch.is_numeric());
println!(" is_alphanumeric: {}", ch.is_alphanumeric());
println!(" is_whitespace: {}", ch.is_whitespace());
println!(" is_ascii: {}", ch.is_ascii());
println!(" is_ascii_digit: {}", ch.is_ascii_digit());
println!(" is_ascii_alphabetic: {}", ch.is_ascii_alphabetic());
}
}
Output:
Character: 'A'
is_alphabetic: true
is_numeric: false
is_alphanumeric: true
is_whitespace: false
is_ascii: true
is_ascii_digit: false
is_ascii_alphabetic: true
Character: 'z'
is_alphabetic: true
is_numeric: false
is_alphanumeric: true
is_whitespace: false
is_ascii: true
is_ascii_digit: false
is_ascii_alphabetic: true
Character: '5'
is_alphabetic: false
is_numeric: true
is_alphanumeric: true
is_whitespace: false
is_ascii: true
is_ascii_digit: true
is_ascii_alphabetic: false
Character: ' '
is_alphabetic: false
is_numeric: false
is_alphanumeric: false
is_whitespace: true
is_ascii: true
is_ascii_digit: false
is_ascii_alphabetic: false
Character: '!'
is_alphabetic: false
is_numeric: false
is_alphanumeric: false
is_whitespace: false
is_ascii: true
is_ascii_digit: false
is_ascii_alphabetic: false
Character: 'δΈ'
is_alphabetic: true
is_numeric: false
is_alphanumeric: true
is_whitespace: false
is_ascii: false
is_ascii_digit: false
is_ascii_alphabetic: false
Character: 'π'
is_alphabetic: false
is_numeric: false
is_alphanumeric: false
is_whitespace: false
is_ascii: false
is_ascii_digit: false
is_ascii_alphabetic: false
Case Conversion
fn main() {
let chars = ['A', 'z', 'Γ', 'Γ', '5'];
for ch in chars {
println!("Original: '{}'", ch);
// Convert to uppercase
let uppercase: String = ch.to_uppercase().collect();
println!(" Uppercase: '{}'", uppercase);
// Convert to lowercase
let lowercase: String = ch.to_lowercase().collect();
println!(" Lowercase: '{}'", lowercase);
// ASCII-only conversions (faster)
if ch.is_ascii() {
println!(" ASCII uppercase: '{}'", ch.to_ascii_uppercase());
println!(" ASCII lowercase: '{}'", ch.to_ascii_lowercase());
}
println!();
}
}
Output:
Original: 'A'
Uppercase: 'A'
Lowercase: 'a'
ASCII uppercase: 'A'
ASCII lowercase: 'a'
Original: 'z'
Uppercase: 'Z'
Lowercase: 'z'
ASCII uppercase: 'Z'
ASCII lowercase: 'z'
Original: 'Γ'
Uppercase: 'Γ'
Lowercase: 'Γ±'
Original: 'Γ'
Uppercase: 'SS'
Lowercase: 'Γ'
Original: '5'
Uppercase: '5'
Lowercase: '5'
ASCII uppercase: '5'
ASCII lowercase: '5'
Character Validation and Conversion
fn main() {
// Digit conversion
let digit_char = '7';
if let Some(digit_value) = digit_char.to_digit(10) {
println!("'{}' as digit: {}", digit_char, digit_value);
}
// Hex digit conversion
let hex_char = 'F';
if let Some(hex_value) = hex_char.to_digit(16) {
println!("'{}' as hex digit: {}", hex_char, hex_value);
}
// Create digit character
if let Some(new_char) = char::from_digit(9, 10) {
println!("Digit 9 as char: '{}'", new_char);
}
// Invalid conversions
let invalid_digit = 'G';
match invalid_digit.to_digit(10) {
Some(value) => println!("'{}' as digit: {}", invalid_digit, value),
None => println!("'{}' is not a valid decimal digit", invalid_digit),
}
}
Output:
'7' as digit: 7
'F' as hex digit: 15
Digit 9 as char: '9'
'G' is not a valid decimal digit
Practical Examples
Character Processing Function
fn analyze_character(ch: char) -> String {
let mut description = String::new();
if ch.is_ascii_alphabetic() {
description.push_str("ASCII letter");
if ch.is_ascii_uppercase() {
description.push_str(" (uppercase)");
} else {
description.push_str(" (lowercase)");
}
} else if ch.is_alphabetic() {
description.push_str("Unicode letter");
} else if ch.is_ascii_digit() {
description.push_str("ASCII digit");
} else if ch.is_numeric() {
description.push_str("Unicode numeric");
} else if ch.is_whitespace() {
description.push_str("Whitespace");
} else if ch.is_ascii_punctuation() {
description.push_str("ASCII punctuation");
} else {
description.push_str("Other Unicode character");
}
description
}
fn main() {
let test_chars = ['A', 'Γ±', '5', 'Β½', ' ', '!', 'δΈ', 'π'];
for ch in test_chars {
println!("'{}': {}", ch, analyze_character(ch));
}
}
Output:
'A': ASCII letter (uppercase)
'Γ±': Unicode letter
'5': ASCII digit
'Β½': Unicode numeric
' ': Whitespace
'!': ASCII punctuation
'δΈ': Unicode letter
'π': Other Unicode character
Boolean Logic Example
fn can_access_system(age: u8, has_badge: bool, is_employee: bool, is_weekend: bool) -> bool {
let is_adult = age >= 18;
let is_authorized = has_badge && is_employee;
let is_business_hours = !is_weekend;
// Access granted if adult AND authorized AND (business hours OR has special access)
is_adult && is_authorized && (is_business_hours || age >= 21)
}
fn main() {
let scenarios = [
(17, true, true, false), // Minor employee, weekday
(20, true, true, false), // Adult employee, weekday
(20, true, true, true), // Adult employee, weekend
(25, false, true, false), // Adult without badge
(22, true, true, true), // Adult employee with special access, weekend
];
for (age, badge, employee, weekend) in scenarios {
let access = can_access_system(age, badge, employee, weekend);
let day_type = if weekend { "weekend" } else { "weekday" };
println!("Age {}, badge: {}, employee: {}, {}: Access {}",
age, badge, employee, day_type,
if access { "GRANTED" } else { "DENIED" });
}
}
Output:
Age 17, badge: true, employee: true, weekday: Access DENIED
Age 20, badge: true, employee: true, weekday: Access GRANTED
Age 20, badge: true, employee: true, weekend: Access DENIED
Age 25, badge: false, employee: true, weekday: Access DENIED
Age 22, badge: true, employee: true, weekend: Access GRANTED
Common Pitfalls
Character Size Assumptions
fn main() {
// char is always 4 bytes in Rust (Unicode scalar value)
println!("Size of char: {} bytes", std::mem::size_of::());
// This is different from C/C++ where char is 1 byte
let ascii_char = 'A';
let unicode_char = 'π';
println!("'A' takes {} bytes in memory", std::mem::size_of_val(&ascii_char));
println!("'π' takes {} bytes in memory", std::mem::size_of_val(&unicode_char));
// For UTF-8 encoding, use strings
let string_ascii = "A";
let string_unicode = "π";
println!("\"A\" as UTF-8: {} bytes", string_ascii.len());
println!("\"π\" as UTF-8: {} bytes", string_unicode.len());
}
Output:
Size of char: 4 bytes
'A' takes 4 bytes in memory
'π' takes 4 bytes in memory
"A" as UTF-8: 1 bytes
"π" as UTF-8: 4 bytes
Checks for Understanding
Question 1
What will this expression evaluate to?
let result = false || true && false;
Click to see answer
false
. The && operator has higher precedence than ||, so this evaluates as false || (true && false)
which is false || false
= false
.
Question 2
How many bytes does a Rust char take in memory, and why?
Click to see answer
4 bytes. Rust's char represents a Unicode scalar value, which requires up to 4 bytes to represent all possible Unicode characters. This is different from C/C++ where char is typically 1 byte.
Question 3
What's the difference between ch.to_uppercase()
and ch.to_ascii_uppercase()
?
Click to see answer
to_uppercase()
handles full Unicode case conversion and returns an iterator (may produce multiple characters, like Γ β SS). to_ascii_uppercase()
only works on ASCII characters and returns a single char, but is faster.