Struct trong Rust
Bài đăng này đã không được cập nhật trong 2 năm
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)]
struct User {
active: bool,
username: String,
email: String,
sign_in_count: u64,
}
fn main() {
let mut 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à username
và email
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 active
sign_in_count
lưu trên Stack
, username
email
lưu trên Heap
hay là lưu toàn bộ active
sign_in_count
username
email
đề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)]
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;
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 đượcclone
thì phải khai báo#[derive(Clone)]
choStruct
đó
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)]
struct Location {
x: i32,
y: i32,
}
fn main() {
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 for more 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)]
struct Rectangle {
width: u32,
height: u32,
}
fn main() {
let rect1 = Rectangle {
width: 30,
height: 50,
};
let rect2 = rect1.clone();
let rect3 = rect2;
println!("{:#?}", rect1);
println!("{:#?}", rect2); // ERRO
println!("{:#?}", 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)]
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 = 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)]
struct User {
active: bool,
username: String,
email: String,
sign_in_count: u64,
}
fn main() {
// --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:
struct AlwaysEqual;
fn main() {
let subject = AlwaysEqual;
}
Methods và Associated Functions
Định nghĩa accociated functions
đơn giản như sau:
#[derive(Debug)]
struct Rectangle {
width: u32,
height: u32,
}
impl Rectangle {
fn square(size: u32) -> Self {
Self {
width: size,
height: size,
}
}
}
fn main() {
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)]
struct Rectangle {
width: u32,
height: u32,
}
impl Rectangle {
fn area(&self) -> u32 {
self.width * self.height
}
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));
}
Cũng tách thành 2 impl
như sau:
#[derive(Debug)]
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);
}
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);
}
All rights reserved