🦀 Rust Cơ Bản – Bài 3: Rust Types

4 min read

Struct, Enum, Pattern Matching và mô hình hóa dữ liệu mạnh mẽ

Nếu Ownership là trái tim của Rust, thì hệ thống kiểu chính là bộ khung giúp Rust trở thành một ngôn ngữ an toàn và diễn đạt ý tưởng rất rõ ràng. Ở Rust, kiểu dữ liệu không chỉ là cách mô tả cấu trúc mà còn thể hiện chính xác ý nghĩa và trạng thái của dữ liệu.

Trong bài viết này, chúng ta sẽ khám phá cách Rust tổ chức dữ liệu thông qua kiểu cơ bản, struct, enum và pattern matching. Đây là phần khiến Rust được xem là vừa an toàn vừa biểu đạt, khác biệt rõ rệt so với C, C++ hoặc Java.

1. Kiểu dữ liệu cơ bản của Rust và cách Rust xác định kiểu

Rust là ngôn ngữ static type, nghĩa là kiểu được xác định tại compile time. Tuy nhiên Rust không buộc bạn phải luôn chỉ rõ mọi thứ. Trình biên dịch rất thông minh trong việc suy luận kiểu.

Ví dụ:

let x = 10;      // x: i32
let y = 3.14;    // y: f64
let msg = "hi";  // msg: &str

Rust chọn kiểu mặc định theo cách tối ưu: integer mặc định là i32, float mặc định là f64. Khi cần chỉ rõ, bạn có thể gắn kiểu tường minh:

let flag: bool = true;
let price: u64 = 1_000_000;

Rust có các kiểu cơ bản như integer, float, bool, char, tuple, array và slice. Tuy nhiên sức mạnh thực sự của hệ thống kiểu không nằm ở các kiểu này mà nằm ở cách Rust cho phép bạn định nghĩa kiểu mới.

2. Struct

Struct trong Rust giống với “data class” trong một số ngôn ngữ khác, nhưng mạnh mẽ và linh hoạt hơn. Struct cho phép bạn tạo kiểu tùy chỉnh thể hiện dữ liệu có cấu trúc.

Ví dụ một cầu thủ:

struct Player {
    name: String,
    number: u8,
    position: String,
}

Tạo instance:

let p = Player {
    name: String::from("Messi"),
    number: 10,
    position: String::from("Forward"),
};

Struct thể hiện dữ liệu rất rõ ràng và tường minh. Rust còn cho phép bạn định nghĩa phương thức bằng khối impl:

impl Player {
    fn description(&self) -> String {
        format!("{} wears number {}", self.name, self.number)
    }
}

Bằng cách đưa logic vào trong impl, struct trở thành đối tượng đầy đủ. Tuy nhiên Rust vẫn không phải là OOP theo nghĩa truyền thống, vì không có kế thừa theo class. Thay vào đó Rust sử dụng traits, nhưng đó sẽ là chủ đề của bài sau.

3. Enum

Nếu struct đại diện cho dữ liệu có cấu trúc, thì enum đại diện cho dữ liệu có nhiều trạng thái. Đây chính là điểm khiến Rust nổi bật. Enum trong Rust mạnh hơn rất nhiều so với enum trong C hoặc Java.

Ví dụ mô hình hóa trạng thái một trận đấu:

enum MatchState {
    NotStarted,
    Playing(u8, u8),      // số bàn thắng
    Finished(u8, u8),
}

Ở trạng thái Playing và Finished, enum mang theo dữ liệu. Nhờ vậy bạn có thể mô tả mọi khả năng theo cách rõ nghĩa và an toàn.

Tạo giá trị enum:

let state = MatchState::Playing(1, 0);

Khả năng đính kèm dữ liệu khiến enum trở thành một mô hình hóa cực kỳ mạnh. Từ hệ thống phân tán, xử lý dữ liệu, game engine cho đến API backend, enum luôn xuất hiện vì nó giúp diễn đạt ý rất rõ và giảm sai sót.

4. Pattern Matching

Khi enum mang dữ liệu, bạn cần một cách để phân tích và xử lý chúng. Rust cung cấp pattern matching thông qua từ khóa match.

Ví dụ tiếp tục từ enum MatchState:

match state {
    MatchState::NotStarted => {
        println!("The match has not started");
    }
    MatchState::Playing(home, away) => {
        println!("Match in progress. Score {} - {}", home, away);
    }
    MatchState::Finished(home, away) => {
        println!("Final score {} - {}", home, away);
    }
}

Pattern matching là điểm rất mạnh vì:

  • compiler buộc bạn xử lý đầy đủ mọi trường hợp
  • tránh quên case
  • không tồn tại kiểu null để gây lỗi bất ngờ
  • mã rõ ràng và có cấu trúc

Đây là điểm khiến Rust thân thiện với lập trình chức năng, nhưng vẫn giữ cú pháp chặt chẽ.

5. Option và Result

Rust không có từ khóa null. Thay vào đó, Rust dùng enum để mô hình hóa giá trị có thể không tồn tại:

enum Option<T> {
    Some(T),
    None,
}

Khi đọc một file hoặc tìm kiếm trong database, bạn sẽ nhận về Option. Điều này buộc bạn phải xử lý cả trường hợp có và không có giá trị, tránh được hàng loạt lỗi kinh điển.

Ví dụ:

let name = Some("Alice");

match name {
    Some(n) => println!("Hello {}", n),
    None => println!("No name provided"),
}

Tương tự, Result mô hình hóa lỗi:

enum Result<T, E> {
    Ok(T),
    Err(E),
}

Mọi thao tác có thể thất bại, như IO hoặc network, đều dùng Result. Điều này buộc bạn phải xử lý lỗi một cách rõ ràng thay vì bỏ qua chúng.

6. Tổng kết

Các cơ chế trong bài này kết hợp tạo ra một hệ thống kiểu rất mạnh:

  • Struct giúp bạn mô tả dữ liệu có cấu trúc.
  • Enum mô tả dữ liệu theo trạng thái, mang kèm thông tin.
  • Pattern matching giúp phân tích logic rõ ràng và đầy đủ.
  • Option và Result loại bỏ hoàn toàn null và giúp quản lý lỗi tường minh.

Nhờ vậy Rust khiến nhiều lỗi phổ biến không thể tồn tại, hoặc bị phát hiện ngay tại compile time. Đây là một trong những lý do Rust được dùng trong hệ thống quan trọng như tiền điện tử, cloud, game engine và backend hiệu năng cao.

Phần 2 của series: https://ant.ncc.plus/?p=20395

Reference:

Avatar photo

Leave a Reply

Your email address will not be published. Required fields are marked *