Struct trong Rust

Ngôn ngữa lập trình nào mà không có Struct. Struct thì cũng na ná với Tuple, nhưng khác ở 1 chỗ: Struct truy cập vào dữ liệu bằng tên trường, còn Tuple thì bằng index. Định nghĩa 1 Struct và khai báo 1 instance của Struct đó đơn giản như sau: #[derive(Debug)]structUser{ active:bool, username:String, email:String,

Ngôn ngữa lập trình nào mà không có Struct.

Struct thì cũng na ná với Tuple, nhưng khác ở 1 chỗ: Struct truy cập vào dữ liệu bằng tên trường, còn Tuple thì bằng index.

Định nghĩa 1 Struct và khai báo 1 instance của Struct đó đơn giản như sau:

#[derive(Debug)]structUser{
    active:bool,
    username:String,
    email:String,
    sign_in_count:u64,}fnmain(){letmut user1 =User{
        email:String::from("someone@example.com"),
        username:String::from("someusername123"),
        active:true,
        sign_in_count:1,};

    user1.email =String::from("anotheremail@example.com");println!("{}", user1.email);println!("{:?}", user1);println!("{:#?}", user1);}

Kết quả:

anotheremail@example.com
User { active: true, username: "someusername123", email: "anotheremail@example.com", sign_in_count: 1}
User {
    active: true,
    username: "someusername123",
    email: "anotheremail@example.com",
    sign_in_count: 1,
}

Đọc code chắc cũng dễ hiểu ha, ở đây chỉ cần chú ý là một Struct muốn in ra màn hình được bằng println!() thì phải có #[derive(Debug)] ở trên đầu và in bằng cú pháp println!("{:?}", user1) hoặc println!("{:#?}", user1)

Ownership với Struct

Oke, bây giờ mình sẽ test thử các tính chất của Ownership đối với User, nhìn vào thì User có 2 trường là usernameemail thuộc kiểu String, như bài trước mình đã nói, một Struct mà trong thành phần của nó có chứa kiểu String hoăc Vector thì giá trị sẽ được lưu trên Heap, mình không biết rõ là nó sẽ lưu theo kiểu activesign_in_count lưu trên Stack, usernameemail lưu trên Heap hay là lưu toàn bộ activesign_in_countusernameemailđều lưu trên Heap, nhưng mà thôi cũng không cần suy cặn kẽ làm gì, vì hành vi của User ở 2 trường hợp này đều giống nhau.

Xét ví dụ:

#[derive(Debug)]structUser{
    active:bool,
    username:String,
    email:String,
    sign_in_count:u64,}fnmain(){let user1 =User{
        email:String::from("someone@example.com"),
        username:String::from("someusername123"),
        active:true,
        sign_in_count:1,};let user2 = user1;println!("{:#?}", user1);// ERROR}

Do giá trị của user1 được lưu trên Heap, nên khi gán let user2 = user1 thì ownership của giá trị đó đã được chuyển từ user1 sang user2 nên println!("{:#?}", user1); sẽ bị lỗi.

Gán không được thì ta dùng clone:

#[derive(Debug, Clone)]
struct User {
    active: bool,
    username: String,
    email: String,
    sign_in_count: u64,
}

fn main() {
    let user1 = User {
        email: String::from("someone@example.com"),
        username: String::from("someusername123"),
        active: true,
        sign_in_count: 1,
    };

    let user2 = user1.clone();

    println!("{:#?}", user1);
    println!("{:#?}", user2);
}

Kết quả

User {
    active: true,
    username: "someusername123",
    email: "someone@example.com",
    sign_in_count: 1,
}
User {
    active: true,
    username: "someusername123",
    email: "someone@example.com",
    sign_in_count: 1,
}

Lưu ý: một Struct muốn sử dụng được clone thì phải khai báo #[derive(Clone)] cho Struct đó

Xét tiếp một ví dụ với struct đơn giản chỉ chứa các trường có kiểu dữ liệu biết trước kích thước:

#[derive(Debug, Clone, Copy)]structLocation{
    x:i32,
    y:i32,}fnmain(){let a =Location{ x:1, y:1};let b = a;println!("{:#?}", a);println!("{:#?}", b);}

Dễ hiểu ha, do Location chỉ chứa 2 trường giá trị được lưu trên Stack nên phép gán không làm ảnh hưởng đến ownership, chỉ có điều một Struct muốn thực hiện phép gán thì phải khai báo #[derive(Clone, Copy)], chú ý có Copy là phải có Clone, có Clone không nhất thiết phải có Copy, nếu có Copy mà không có Clone thì khi compile sẽ hiển thị lỗi sau:

383| pub trait Copy: Clone {|                 ^^^^^ required by this bound in`Copy`= note: this error originates in the derive macro `Copy`(in Nightly builds, run with -Z macro-backtrace formore info)
help: consider annotating `Location` with `#[derive(Clone)]`|10|#[derive(Clone)]|

error: aborting due to previous error

For more information about this error, try `rustc --explain E0277`.

Vậy nếu muốn Location có hành vi như User thì làm như thế nào, đơn giản bỏ Copy đi, lúc này phép gán sẽ chuyển ownership, muốn clone() được thì phải có Clone:

#[derive(Debug, Clone)]structRectangle{
    width:u32,
    height:u32,}fnmain(){let rect1 =Rectangle{
        width:30,
        height:50,};let rect2 = rect1.clone();let rect3 = rect2;println!("{:#?}", rect1);println!("{:#?}", rect2);// ERROprintln!("{:#?}", rect3);}

Vậy tại sao chúng ta lại không thêm Copy cho User struct ở trên để nó có thể gán mà không ảnh hưởng đến ownership, cấm nha, Rust nghiêm cấm add Copy cho struct nào có ít nhất 1 trường có kiểu dữ liệu có kích thước không biết trước – giá trị lưu trên Heap.

Tạo một instance từ một instance khác

Dưới đây là các cách tạo, code cũng dễ hiêu:

#[derive(Debug, Clone)]structUser{
    active:bool,
    username:String,
    email:String,
    sign_in_count:u64,}fnmain(){let user1 =User{
        email:String::from("someone@example.com"),
        username:String::from("someusername123"),
        active:true,
        sign_in_count:1,};let user2 =User{
        active: user1.active,
        username: user1.username.clone(),
        email: user1.email.clone(),
        sign_in_count: user1.sign_in_count,};println!("{:#?}", user1);println!("{:#?}", user2);}
#[derive(Debug)]structUser{
    active:bool,
    username:String,
    email:String,
    sign_in_count:u64,}fnmain(){// --snip--let user1 =User{
        email:String::from("someone@example.com"),
        username:String::from("someusername123"),
        active:true,
        sign_in_count:1,};let user2 =User{
        email:String::from("another@example.com"),
        username:String::from("anotheruser"),..user1
    };println!("{:#?}", user1);println!("{:#?}", user2);}

Unit-like struct

Chúng ta có thể định nghĩa 1 struct mà không có trường nào:

structAlwaysEqual;fnmain(){let subject =AlwaysEqual;}

Methods và Associated Functions

Định nghĩa accociated functions đơn giản như sau:

#[derive(Debug)]structRectangle{
    width:u32,
    height:u32,}implRectangle{fnsquare(size:u32)->Self{Self{
            width: size,
            height: size,}}}fnmain(){let sq =Rectangle::square(3);}

Nhìn cũng dễ hiểu cách định nghĩa và cách sử dụng ha.

Định nghĩa methods thì như sau:

#[derive(Debug)]structRectangle{
    width:u32,
    height:u32,}implRectangle{fnarea(&self)->u32{self.width *self.height
    }fncan_hold(&self, other:&Rectangle)->bool{self.width > other.width &&self.height > other.height
    }}fnmain(){let rect1 =Rectangle{
        width:30,
        height:50,};let rect2 =Rectangle{
        width:10,
        height:40,};let rect3 =Rectangle{
        width:60,
        height:45,};println!("Can rect1 hold rect2? {}", rect1.can_hold(&rect2));println!("Can rect1 hold rect3? {}", rect1.can_hold(&rect3));}

Cũng tách thành 2 impl như sau:

#[derive(Debug)]structRectangle{
    width:u32,
    height:u32,}implRectangle{fnarea(&self)->u32{self.width *self.height
    }}implRectangle{fncan_hold(&self, other:&Rectangle)->bool{self.width > other.width &&self.height > other.height
    }}fnmain(){let rect1 =Rectangle{
        width:30,
        height:50,};let rect2 =Rectangle{
        width:10,
        height:40,};let rect3 =Rectangle{
        width:60,
        height:45,};println!("Can rect1 hold rect2? {}", rect1.can_hold(&rect2));println!("Can rect1 hold rect3? {}", rect1.can_hold(&rect3));println!("{:#?}", rect1);println!("{:#?}", rect2);println!("{:#?}", rect3);}

Nhìn cũng đơn giản dễ hiểu ha, chỉ cần chú ý là ở đây chúng ta đang không khai báo Copy cho Rectangle nên bắt buộc các function trong impl Retangle chúng ta phải dùng reference & để đảm bảo ownership cho các biến.

Do Rectangle chỉ có 2 trường thuộc kiểu dữ liệu i32 nên ta có thể khai báo Copy cho nó, lúc này các function trong impl Retangle không cần phải phải sử dụng reference & nữa mà vẫn bảo toàn ownership của các biến.

#[derive(Debug, Clone, Copy)]
struct Rectangle {
    width: u32,
    height: u32,
}

impl Rectangle {
    fn area(self) -> u32 {
        self.width * self.height
    }
}

impl Rectangle {
    fn can_hold(self, other: Rectangle) -> bool {
        self.width > other.width && self.height > other.height
    }
}

fn main() {
    let rect1 = Rectangle {
        width: 30,
        height: 50,
    };
    let rect2 = Rectangle {
        width: 10,
        height: 40,
    };
    let rect3 = Rectangle {
        width: 60,
        height: 45,
    };

    println!("Can rect1 hold rect2? {}", rect1.can_hold(rect2));
    println!("Can rect1 hold rect3? {}", rect1.can_hold(rect3));
    println!("{:#?}", rect1);
    println!("{:#?}", rect2);
    println!("{:#?}", rect3);
}

Nguồn: viblo.asia

Bài viết liên quan

WebP là gì? Hướng dẫn cách để chuyển hình ảnh jpg, png qua webp

WebP là gì? WebP là một định dạng ảnh hiện đại, được phát triển bởi Google

Điểm khác biệt giữa IPv4 và IPv6 là gì?

IPv4 và IPv6 là hai phiên bản của hệ thống địa chỉ Giao thức Internet (IP). IP l

Check nameservers của tên miền xem website trỏ đúng chưa

Tìm hiểu cách check nameservers của tên miền để xác định tên miền đó đang dùn

Mình đang dùng Google Domains để check tên miền hàng ngày

Từ khi thông báo dịch vụ Google Domains bỏ mác Beta, mình mới để ý và bắt đầ