Mẫu Newtype Trong Rust - Ứng Dụng Thực Tế

Giới thiệu về Newtype

Nói một cách đơn giản, Newtype là việc sử dụng cấu trúc tuple để bao bọc một kiểu dữ liệu đã có: struct KiloMetre(u32)

Lợi ích của Newtype:

  • Kiểu dữ liệu tùy chỉnh giúp chúng ta tạo ra những tên loại có ý nghĩa và dễ đọc hơn, ví dụ struct NamSinh(u32)
  • Trong một số trường hợp, chỉ có Newtype có thể giải quyết tốt
  • Ẩn chi tiết của kiểu dữ liệu bên trong

Tạo kiểu dữ liệu tùy chỉnh với tên có ý nghĩa

struct ThoiGian(i64);

struct Ngay(i64);

impl ThoiGian {
    pub fn chuyenThanhNgay(&self) -> Ngay {
        Ngay(self.0 * 365)
    }
}


impl Ngay {
    /// cắt bỏ các năm không đủ
    pub fn chuyenThanhThoiGian(&self) -> ThoiGian {
        ThoiGian(self.0 / 365)
    }
}


Triển khai trait bên ngoài cho kiểu dữ liệu bên ngoài

Rust giới hạn chỉ cho phép triển khai trait cho một kiểu dữ liệu khi cả hai (kiểu dữ liệu và trait) đều được định nghĩa trong cùng một gói, được gọi là quy tắc cô lập. Ví dụ, bạn không thể triển khai trait fmt::Display cho Vec.

Bằng cách sử dụng kiểu Newtype, chúng ta có thể vượt qua quy tắc cô lập, cụ thể là sử dụng cấu trúc tuple để tạo ra một kiểu dữ liệu mới. Quan trọng là, cách này không gây bất kỳ chi phí thời gian chạy nào, kiểu mới sẽ được tối ưu hóa trong quá trình biên dịch.

use std::{fmt, ops::Deref};

class BaoVec(Vec<String>);

impl fmt::Display for BaoVec {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "[{}]", self.0.join(", "))
    }
}

impl Deref for BaoVec {
    type Target = Vec<String>;

    fn deref(&self) -> &Self::Target {
        &self.0
    }
}

fn main() {
    let b = BaoVec(vec![String::from("xin chào"),
    String::from("thế giới")]);
    println!("b = {}", b);
    println!("bao vec có thể sử dụng phương thức join, kết quả: {}", b.join(","));
}

Vì BaoVec là một kiểu dữ liệu mới, nên nó không có các phương thức của giá trị bên trong. Để BaoVec hoạt động giống hệt Vec, chúng ta cần triển khai tất cả các phương thức của Vec trong BaoVec và ủy thác chúng cho self.0. Nếu chúng ta muốn kiểu mới có tất cả các phương thức của kiểu bên trong, chúng ta cũng có thể triển khai trait Deref cho BaoVec để trả về trực tiếp kiểu bên trong. Ngược lại, nếu chúng ta không muốn kiểu BaoVec có tất cả các phương thức của kiểu bên trong, ví dụ khi cần giới hạn hành vi của kiểu BaoVec, chúng ta chỉ có thể triển khai thủ công các phương thức cần thiết.

Đóng gói chi tiết bên trong

#[derive(Debug)]
class BaoChuoi(String);

fn main() {
    let s = "Xin Chào Thế Giới".to_string();
    let mut b = BaoChuoi(s);
    println!("{:?}", b);
    
    // Ẩn các phương thức của String được bao bọc
    // Nhưng vẫn có thể gọi thông qua cách sau
    // b.0.push_str(" !");
    // println!("{:?}", b);   
}


Thẻ: Rust Newtype Design Pattern Type Safety Abstraction

Đăng vào ngày 17 tháng 05 lúc 00:29