Danh sách bài viết

Bài 35: Boolean: bool, true / false

Bài 35 của series Rust Cơ Bản — kiểu bool trong Rust với hai giá trị duy nhất true và false, layout 1 byte trong memory, các operator logic và bitwise, vì sao Rust KHÔNG cho dùng integer ở vị trí điều kiện như C/JavaScript, cách cast sang số nguyên, và những method tiện lợi như bool::then giúp bridge sang Option<T>.

09/06/2026
9 phút đọc
1 lượt xem
1

Mục Tiêu Bài Học

Sau bài học, bạn sẽ:

  • Nắm cú pháp khai báo bool với hai giá trị true, false và biết tên type lowercase đúng chuẩn (bool, không phải Bool hay boolean).
  • Hiểu vì sao Rust dùng 1 byte để lưu một bool dù về lý thuyết chỉ cần 1 bit, và khi nào cần dùng cấu trúc densely-packed (bitvec crate hoặc u8 mask 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 u32 ra 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ển bool thành Option<T> một cách idiomatic.
2

Cú Pháp bool

bool trong Rust chỉ có hai giá trị duy nhất: truefalse. 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ó truefalse — không có TRUE, True, 1, "yes" nào tương đương.
  • Tên type là bool — viết Bool hay boolean sẽ error: cannot find type.
  • Function trả về bool theo convention bắt đầu bằng is_, has_, can_, contains: is_empty(), has_role(), can_edit(), contains_key().
3

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_bool trả về reference thì my_bool phả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 HashSet kiểu bitset trên dữ liệu ID liên tục — dùng crate bitvec với BitVec, 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ều bool.

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.

4

Operator Logic: !, &&, ||, &, |, ^

Rust phân biệt rõ operator logic short-circuitoperator bitwise eager — cả hai đều dùng được với bool, nhưng ngữ nghĩa khác nhau.

OperatorTênShort-circuit?Áp dụng
!aNOTinvert giá trị
a && bAND logicchỉ bool
a || bOR logicchỉ bool
a & bAND bitwiseKhôngbool và integer
a | bOR bitwiseKhôngbool và integer
a ^ bXOR bitwiseKhôngbool 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 evaluate X.
  • true || X → trả true, KHÔNG evaluate X.
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 &&|| 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 (vd i < v.len() && v[i] > 0 trá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ả).
5

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ữCodeHành vi
Cif (1) { ... }OK - 1 truthy
JavaScriptif (1) { ... }OK - 1 truthy
Pythonif 1:OK - 1 truthy
Goif 1 { ... }Compile error - giống Rust
Rustif 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.

6

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;
7

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áo x == true hoặc x == false, đề xuất rút gọn thành x hoặc !x.
  • clippy::needless_bool — cảnh báo if cond { true } else { false }, đề xuất viết thẳng cond.
  • clippy::nonminimal_bool — phát hiện biểu thức boolean rườm rà như !(!a)a, a && (a || b)a.
8

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ỉ evaluate expr khi self == 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()
}
9

bool Trong Pattern Matching

bool được match theo hai pattern truefalse — 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.

10

Tổng Kết

  • bool chỉ 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 bitvec crate hoặc u8 mask 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/i64 ra 0/1 — hữu ích cho đếm. Không có cast ngược int → bool, viết tay qua so sánh.
  • if/while/match yêu cầu bool strict. Tránh x == true / x == false redundant (clippy bool_comparison).
  • bool::then(|| value) lazy, bool::then_some(value) eager — chuyển bool sang Option<T> idiomatic.
  • match bool chỉ 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.
11

Bài Tập Củng Cố

Tự trả lời, đáp án ở cuối:

  1. Viết một function fn is_valid_age(age: i32) -> bool trả về true nếu age trong khoảng 0..=150, ngược lại false. Có ít nhất 2 cách viết, chọn cách ngắn nhất.
  2. Đ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 };
  3. Vì sao let b: bool = 1 as bool; không compile? Đề xuất cách chuyển i32 sang bool đúng idiomatic.
  4. 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ùng then chứ không phải then_some ở đây?
  5. 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
  1. 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ánh if cond { true } else { false } (clippy needless_bool).
  2. Không in gì cả. false && { ... }: vế trái false nên short-circuit, block không chạy, r = false. true || { ... }: vế trái true nê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.
  3. 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ặc let b: bool = n == 1; tuỳ ngữ nghĩa mong muốn.
  4. fn get_msg(ok: bool) -> Option<String> { ok.then(|| String::from("done")) }. Dùng then(||)String::from("done")heap allocation — nếu dùng then_some(String::from("done")) thì allocation xảy ra LUÔN dù ok = false (eager), lãng phí. then_some chỉ phù hợp khi value rẻ (literal, Copy).
  5. 8 byte (8 bool × 1 byte, không có padding vì align của bool là 1). Muốn pack thành 1 byte: dùng u8 với bit field thủ công (const FLAG_A: u8 = 1 << 0; const FLAG_B: u8 = 1 << 1; ...) và lưu một u8 duy nhất; hoặc dùng macro crate bitflags để generate API type-safe; hoặc bitvec::BitArray nếu số flag biến động lớn hơn 8.
12

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...).