Mục lục
Mục Tiêu Bài Học
Sau bài học, bạn sẽ:
- Nắm cú pháp khai báo
boolvới hai giá trịtrue,falsevà biết tên type lowercase đúng chuẩn (bool, không phảiBoolhayboolean). - Hiểu vì sao Rust dùng 1 byte để lưu một
booldù về lý thuyết chỉ cần 1 bit, và khi nào cần dùng cấu trúc densely-packed (bitveccrate hoặcu8mask thủ công). - Phân biệt operator logic short-circuit (
&&,||) với bitwise eager (&,|,^) và biết khi nào nên dùng loại nào. - Hiểu vì sao
if 1 { }không compile trong Rust — khác C/JavaScript với khái niệm "truthy/falsy". - Biết cast
bool as u8/as u32ra 0/1 để đếm phần tử true trong slice, và vì sao chiều ngược lại (int → bool) phải viết tay qua so sánh. - Dùng method
bool::then,bool::then_someđể chuyểnboolthànhOption<T>một cách idiomatic.
Cú Pháp bool
bool trong Rust chỉ có hai giá trị duy nhất: true và false. Tên type viết lowercase, hai literal cũng lowercase (khác Python True/False hay Java Boolean.TRUE).
// Khai báo cơ bản - có annotation
let on: bool = true;
let off: bool = false;
// Type inference - Rust tự suy ra bool từ literal
let is_admin = true; // bool
let is_logged_in = false; // bool
// Tổ hợp operator cho ra bool
let a = true;
let b = false;
let not_a = !a; // false (NOT)
let a_and_b = a && b; // false (AND)
let a_or_b = a || b; // true (OR)
let a_xor_b = a ^ b; // true (XOR bitwise)
println!("{not_a} {a_and_b} {a_or_b} {a_xor_b}");
// bool là kết quả phổ biến nhất của comparison
let score = 85;
let passed: bool = score >= 60;
let perfect: bool = score == 100;
let in_range: bool = (0..=100).contains(&score);
Một vài điểm Rust nghiêm hơn các ngôn ngữ khác:
- Chỉ có
truevàfalse— không cóTRUE,True,1,"yes"nào tương đương. - Tên type là
bool— viếtBoolhaybooleansẽerror: cannot find type. - Function trả về
booltheo convention bắt đầu bằngis_,has_,can_,contains:is_empty(),has_role(),can_edit(),contains_key().
Memory Representation - 1 Byte Đầy Đủ
Về lý thuyết, một bool chỉ cần 1 bit để lưu (0 hoặc 1). Nhưng trong thực tế Rust dành nguyên 1 byte (8 bit) cho mỗi giá trị bool — tương tự C/C++.
use std::mem::size_of;
fn main() {
println!("size_of::<bool>() = {}", size_of::<bool>());
// In ra: size_of::<bool>() = 1
// Trong struct, mỗi bool chiếm 1 byte (chưa kể padding)
struct Flags {
a: bool,
b: bool,
c: bool,
}
println!("size_of::<Flags>() = {}", size_of::<Flags>());
// In ra: 3 (3 bool x 1 byte, không pack thành 3 bit)
}
Lý do hardware-friendly:
- CPU không có instruction load/store single bit trực tiếp — phải đọc nguyên byte rồi mask. Dùng nguyên byte thì load 1 instruction, dùng bit packing phải shift + mask thêm.
- Address-ability: smallest addressable unit của RAM là 1 byte. Muốn
&my_booltrả về reference thìmy_boolphải có địa chỉ riêng — không pack chung với bool khác. - Cache line: CPU đọc theo block 64 byte. Lợi ích từ bit packing chỉ thực sự đáng kể khi có hàng triệu bool liên tục.
Khi nào cần densely-packed (1 bit / phần tử)?
- Cấu trúc
HashSetkiểu bitset trên dữ liệu ID liên tục — dùng cratebitvecvớiBitVec,BitArray. - Bit field thủ công với
u8/u32+ operator mask:flags |= 1 << 2;,let bit3 = (flags >> 3) & 1;— phổ biến trong code FFI, parse network packet. - Lưu state machine compact qua
enum+#[repr(u8)]thay vì dùng nhiềubool.
Hệ quả: nếu bạn có struct với 10 field bool, Rust sẽ dùng ít nhất 10 byte (cộng padding). Trong 99% trường hợp đây không phải vấn đề — chỉ tối ưu khi profiling cho thấy memory là bottleneck.
Operator Logic: !, &&, ||, &, |, ^
Rust phân biệt rõ operator logic short-circuit và operator bitwise eager — cả hai đều dùng được với bool, nhưng ngữ nghĩa khác nhau.
| Operator | Tên | Short-circuit? | Áp dụng |
|---|---|---|---|
!a | NOT | — | invert giá trị |
a && b | AND logic | Có | chỉ bool |
a || b | OR logic | Có | chỉ bool |
a & b | AND bitwise | Không | bool và integer |
a | b | OR bitwise | Không | bool và integer |
a ^ b | XOR bitwise | Không | bool và integer |
Short-circuit nghĩa là vế phải chỉ được evaluate khi kết quả vế trái chưa quyết định được toàn biểu thức:
false && X→ trảfalse, KHÔNG evaluateX.true || X→ trảtrue, KHÔNG evaluateX.
fn side_effect(label: &str) -> bool {
println!(" -> goi side_effect({label})");
true
}
fn main() {
println!("Test && voi ve trai false:");
let r1 = false && side_effect("A");
println!("ket qua = {r1}\n");
// Output: KHONG in dong "-> goi side_effect(A)"
// ket qua = false
println!("Test || voi ve trai true:");
let r2 = true || side_effect("B");
println!("ket qua = {r2}\n");
// Output: KHONG in dong "-> goi side_effect(B)"
// ket qua = true
println!("Test & bitwise voi ve trai false:");
let r3 = false & side_effect("C");
println!("ket qua = {r3}\n");
// Output: VAN in dong "-> goi side_effect(C)" - eager
// ket qua = false
println!("Test | bitwise voi ve trai true:");
let r4 = true | side_effect("D");
println!("ket qua = {r4}");
// Output: VAN in dong "-> goi side_effect(D)" - eager
// ket qua = true
}
Khi nào dùng cái nào (idiom Rust):
- Mặc định dùng
&&và||cho điều kiện logic — short-circuit giúp tránh gọi hàm không cần thiết, và đặc biệt quan trọng khi vế phải có thể panic (vdi < v.len() && v[i] > 0tránh index out-of-bounds). - Dùng
&,|,^khi (a) muốn evaluate cả hai vế để side-effect cùng chạy; (b) thao tác bitwise trên integer; (c) bị clippy nhắc thay bằng bitwise nếu cả hai vế không có nhánh phụ. ^đặc biệt hữu ích cho XOR — không có phiên bản^^short-circuit (vì XOR luôn cần biết cả hai vế mới ra kết quả).
bool KHÔNG Auto Convert Sang Int
Khác C, C++, JavaScript, Python — Rust không có khái niệm "truthy/falsy". Integer KHÔNG tự convert sang bool, và ngược lại. Điều kiện trong if/while phải là bool thuần.
fn main() {
let x = 1;
if x { // COMPILE ERROR
println!("non-zero");
}
}
Output cargo build:
error[E0308]: mismatched types
--> src/main.rs:2:8
|
2 | if 1 {
| ^ expected `bool`, found integer
So sánh với các ngôn ngữ khác:
| Ngôn ngữ | Code | Hành vi |
|---|---|---|
| C | if (1) { ... } | OK - 1 truthy |
| JavaScript | if (1) { ... } | OK - 1 truthy |
| Python | if 1: | OK - 1 truthy |
| Go | if 1 { ... } | Compile error - giống Rust |
| Rust | if 1 { ... } | Compile error |
Cách fix: viết so sánh tường minh.
let x = 1;
// Đúng - so sánh ra bool rõ ràng
if x != 0 {
println!("non-zero");
}
if x > 0 {
println!("positive");
}
// Với Option/Result dùng method dedicated
let opt: Option<i32> = Some(5);
if opt.is_some() { /* ... */ }
let res: Result<i32, String> = Ok(5);
if res.is_ok() { /* ... */ }
// Với String / Vec
let s = String::from("hi");
if !s.is_empty() { /* ... */ }
Lợi ích của strict typing này: không có bug kiểu if (x = 5) (gán nhầm) như C — vì x = 5 trả về () chứ không phải bool, sẽ compile error ngay. Tránh được cả lớp lỗi "truthy bất ngờ" như if ("0") truthy trong JavaScript.
Cast bool Sang Integer
Tuy không auto convert, Rust cho phép cast tường minh bool sang integer bằng as:
let t = true;
let f = false;
let a: u8 = t as u8; // 1
let b: u8 = f as u8; // 0
let c: u32 = t as u32; // 1
let d: i64 = f as i64; // 0
Quy ước cố định: true → 1, false → 0. Cast hữu ích cho đếm số phần tử true trong slice mà không cần if/else:
fn main() {
let flags = [true, false, true, true, false, true];
// Đếm số true bằng cast bool -> u32 rồi sum
let true_count: u32 = flags.iter().map(|&b| b as u32).sum();
println!("So phan tu true: {true_count}"); // 4
// Tương đương: dùng filter + count
let true_count2 = flags.iter().filter(|&&b| b).count();
println!("So phan tu true: {true_count2}"); // 4
// Idiom Rust mới: bool::then_some + count
// (xem section 8)
}
Chú ý: không có cast theo chiều ngược lại. let b: bool = 1 as bool; sẽ compile error casting `i32` as `bool` is invalid. Phải viết tay qua so sánh:
let n = 1;
let b: bool = n != 0; // chuyển int -> bool
// Tránh viết kiểu:
// let b: bool = if n == 1 { true } else { false };
// -> clippy::needless_bool, dùng `n == 1` luôn
let b: bool = n == 1;
if / while / match Yêu Cầu bool Strict
Mọi cấu trúc điều khiển dựa trên điều kiện đều yêu cầu biểu thức kiểu bool:
let n = 10;
// if cần bool
if n > 0 { /* ... */ }
if n > 0 && n < 100 { /* ... */ }
// while cần bool
let mut i = 0;
while i < n {
i += 1;
}
// loop infinite (không có condition) - bool không liên quan
loop {
break;
}
// for KHÔNG cần bool - dùng iterator pattern (sẽ học sâu ở Group 7)
for x in 0..n { /* ... */ }
// Tránh redundant comparison với true/false
let is_admin = true;
if is_admin == true { /* ... */ } // clippy warn: bool_comparison
if is_admin == false { /* ... */ } // clippy warn
// Idiom đúng:
if is_admin { /* ... */ }
if !is_admin { /* ... */ }
Clippy lint cụ thể:
clippy::bool_comparison— cảnh báox == truehoặcx == false, đề xuất rút gọn thànhxhoặc!x.clippy::needless_bool— cảnh báoif cond { true } else { false }, đề xuất viết thẳngcond.clippy::nonminimal_bool— phát hiện biểu thức boolean rườm rà như!(!a)→a,a && (a || b)→a.
Method Trên bool: then, then_some
Stdlib có hai method tiện lợi giúp bridge bool sang Option<T> — thay cho idiom if cond { Some(x) } else { None }.
bool::then(|| expr)— lazy: nhận closure, chỉ evaluateexprkhiself == true.bool::then_some(value)— eager: nhận value đã sẵn, không cần closure (ổn định từ Rust 1.62).
fn classify(score: u32) -> Option<&'static str> {
(score >= 90).then(|| "excellent")
}
fn main() {
let result = classify(95); // Some("excellent")
let none = classify(50); // None
println!("{result:?} {none:?}");
}
So sánh hai version:
fn expensive() -> String {
println!("dang tinh expensive...");
String::from("data")
}
fn main() {
let cond = false;
// then - lazy, KHONG goi expensive() khi cond = false
let v1: Option<String> = cond.then(|| expensive());
// then_some - eager, neu truyen expensive() truc tiep
// se goi LUON du cond = false
let v2: Option<String> = cond.then_some(String::from("data"));
// Khi value re (literal, Copy) -> then_some ngan gon hon
let n: Option<u32> = (1 > 0).then_some(42); // Some(42)
// Khi value DAT (allocation, function call) -> then(||) lazy
let s: Option<String> = (1 > 0).then(|| String::from("hello"));
println!("{v1:?} {v2:?} {n:?} {s:?}");
}
Use case phổ biến: biến điều kiện thành Option để chain với ?, map, and_then trong pipeline xử lý data.
// Trước: rườm rà
fn get_role(user_id: u64, is_admin: bool) -> Option<String> {
if is_admin {
Some(format!("admin_{user_id}"))
} else {
None
}
}
// Sau: dùng then
fn get_role_v2(user_id: u64, is_admin: bool) -> Option<String> {
is_admin.then(|| format!("admin_{user_id}"))
}
// Đếm số true trong slice bằng then_some + sum
fn count_true(flags: &[bool]) -> u32 {
flags.iter()
.filter_map(|&b| b.then_some(1u32))
.sum()
}
bool Trong Pattern Matching
bool được match theo hai pattern true và false — exhaustive khi liệt kê cả hai.
fn main() {
let is_admin = true;
let role = match is_admin {
true => "Admin",
false => "User",
};
println!("Role: {role}");
// KHONG can _ wildcard - 2 pattern da exhaustive
// Neu thieu mot pattern, compile error:
// error[E0004]: non-exhaustive patterns: `false` not covered
}
Trong đa số trường hợp với bool đơn giản, if/else ngắn hơn match — nên là lựa chọn mặc định. match trên bool thường chỉ phù hợp khi destructure cấu trúc chứa bool:
struct AccountState {
is_admin: bool,
is_active: bool,
}
fn describe(state: AccountState) -> &'static str {
match (state.is_admin, state.is_active) {
(true, true) => "Active admin",
(true, false) => "Suspended admin",
(false, true) => "Active user",
(false, false) => "Suspended user",
}
}
// Match guard với bool: ket hop pattern khac
fn check_score(passed: bool, score: u32) -> &'static str {
match score {
s if passed && s >= 90 => "Distinction",
s if passed && s >= 60 => "Pass",
_ if !passed => "Fail",
_ => "Edge case",
}
}
fn main() {
let a = AccountState { is_admin: true, is_active: true };
println!("{}", describe(a)); // Active admin
println!("{}", check_score(true, 95)); // Distinction
println!("{}", check_score(false, 80)); // Fail
}
Tip thực tế: nếu thấy bạn đang viết match bool với 2 nhánh đơn giản trả về value, cân nhắc bool::then hoặc ternary-style if/else expression. match phát huy giá trị khi tuple/struct destructure hoặc combo guard phức tạp.
Tổng Kết
boolchỉ có hai giá trị:true,false. Tên type lowercase.- Memory: 1 byte / bool dù chỉ cần 1 bit — lý do CPU access nhanh và addressability. Cần densely-packed thì dùng
bitveccrate hoặcu8mask thủ công. - Operator logic
&&,||short-circuit — vế phải không evaluate nếu vế trái đã quyết định. Bitwise&,|,^eager — luôn evaluate cả hai vế. !là NOT. Idiom mặc định:&&,||. Dùng bitwise khi cần evaluate cả hai vế hoặc thao tác trên integer.- Rust KHÔNG có truthy/falsy —
if 1 { }compile error. Phải so sánh tường minh (n != 0,opt.is_some()). - Cast
bool as u8/u32/i64ra 0/1 — hữu ích cho đếm. Không có cast ngược int → bool, viết tay qua so sánh. if/while/matchyêu cầuboolstrict. Tránhx == true/x == falseredundant (clippybool_comparison).bool::then(|| value)lazy,bool::then_some(value)eager — chuyển bool sangOption<T>idiomatic.match boolchỉ thực sự đáng dùng khi destructure tuple/struct chứa nhiều bool hoặc kết hợp guard phức tạp.
Bài Tập Củng Cố
Tự trả lời, đáp án ở cuối:
- Viết một function
fn is_valid_age(age: i32) -> booltrả vềtruenếuagetrong khoảng 0..=150, ngược lạifalse. Có ít nhất 2 cách viết, chọn cách ngắn nhất. - Đoạn code sau in ra gì? Giải thích vì sao:
let r = false && { println!("X"); true }; let s = true || { println!("Y"); false }; - Vì sao
let b: bool = 1 as bool;không compile? Đề xuất cách chuyểni32sangboolđúng idiomatic. - Refactor đoạn sau dùng
bool::then:fn get_msg(ok: bool) -> Option<String> { if ok { Some(String::from("done")) } else { None } }. Vì sao nên dùngthenchứ không phảithen_someở đây? - Trong struct
Flags { a: bool, b: bool, c: bool, d: bool, e: bool, f: bool, g: bool, h: bool }, tổng kích thước memory bao nhiêu byte? Nếu muốn pack thành 1 byte (8 bit), dùng cấu trúc gì?
Đáp án
fn is_valid_age(age: i32) -> bool { (0..=150).contains(&age) }— ngắn và rõ. Hai cách khác:age >= 0 && age <= 150, hoặc match range pattern. Tránhif cond { true } else { false }(clippyneedless_bool).- Không in gì cả.
false && { ... }: vế tráifalsenên short-circuit, block không chạy,r = false.true || { ... }: vế tráitruenên short-circuit, block không chạy,s = true. Đây là minh hoạ cốt lõi của short-circuit: side-effect trong vế phải có thể không bao giờ xảy ra. - Rust không cho cast trực tiếp integer → bool vì không có quy ước rõ giá trị nào là true (chỉ 1? hay mọi non-zero?). Phải viết so sánh tường minh:
let b: bool = n != 0;hoặclet b: bool = n == 1;tuỳ ngữ nghĩa mong muốn. fn get_msg(ok: bool) -> Option<String> { ok.then(|| String::from("done")) }. Dùngthen(||)vìString::from("done")là heap allocation — nếu dùngthen_some(String::from("done"))thì allocation xảy ra LUÔN dùok = false(eager), lãng phí.then_somechỉ phù hợp khi value rẻ (literal, Copy).- 8 byte (8 bool × 1 byte, không có padding vì align của
boollà 1). Muốn pack thành 1 byte: dùngu8với bit field thủ công (const FLAG_A: u8 = 1 << 0; const FLAG_B: u8 = 1 << 1; ...) và lưu mộtu8duy nhất; hoặc dùng macro cratebitflagsđể generate API type-safe; hoặcbitvec::BitArraynếu số flag biến động lớn hơn 8.
Bài Tiếp Theo
Bài 36: Character: char Là 4 Bytes Unicode Scalar — kiểu char trong Rust khác hẳn C (1 byte): mỗi char là một Unicode Scalar Value 4 byte, lưu được mọi ký tự từ 'a' đến '😀' đến '中'; phân biệt rõ char với UTF-8 byte trong String, và các method classify ký tự (is_alphabetic, to_uppercase...).
