A struct is a custom data type that lets you name and package together multiple related values into a meaningful unit. If you are familiar with object-oriented programming, a struct is like the data attributes of a class — but without inheritance.
Struct là kiểu dữ liệu tùy chỉnh cho phép bạn đặt tên và nhóm nhiều giá trị liên quan lại thành một đơn vị có ý nghĩa. Nếu bạn quen với lập trình hướng đối tượng, struct giống như các thuộc tính dữ liệu của một class — nhưng không có kế thừa.
1// Define a struct with named fields2struct User {3 active: bool,4 username: String,5 email: String,6 sign_in_count: u64,7}89fn main() {10 // Instantiate a struct — ALL fields must be provided11 let user1 = User {12 active: true,Trong Rust, không thể đánh dấu chỉ một số trường nhất định là có thể thay đổi. Tính biến đổi (mutability) áp dụng cho toàn bộ instance của struct — không có trường-level mutability.
When a function parameter name and a struct field name are identical, you can use the field init shorthand syntax to avoid repeating the name. This is very common in constructor functions.
Khi tên tham số hàm và tên trường struct trùng nhau, bạn có thể dùng cú pháp khởi tạo ngắn gọn (field init shorthand) để tránh lặp lại tên. Điều này rất phổ biến trong các hàm constructor.
1struct User {2 active: bool,3 username: String,4 email: String,5 sign_in_count: u64,6}78// Without shorthand — repetitive9fn build_user_verbose(email: String, username: String) -> User {10 User {11 active: true,12 username: username, // redundantStruct update syntax allows creating a new instance from an old one, overriding some fields while taking the remaining fields from the original instance. The .. syntax must come last and specifies the source for fields not explicitly listed.
Cú pháp cập nhật struct cho phép tạo instance mới từ instance cũ, ghi đè một số trường trong khi lấy các trường còn lại từ instance gốc. Cú pháp .. phải đứng cuối cùng và chỉ định nguồn cho các trường không được liệt kê tường minh.
1struct User {2 active: bool,3 username: String,4 email: String,5 sign_in_count: u64,6}78fn main() {9 let user1 = User {10 active: true,11 username: String::from("user1"),12 email: String::from("user1@example.com"),Khi dùng cú pháp cập nhật struct, các trường có kiểu triển khai Copy (như bool, u64) được sao chép, còn các trường không triển khai Copy (như String) bị di chuyển. Sau move, instance gốc có thể không còn hoàn toàn hợp lệ.
Tuple structs look like regular structs but their fields have no names — only types. They are useful when you want to give the whole tuple a distinct name and differentiate it from other tuples with the same type structure. Each tuple struct is its own distinct type.
Tuple struct trông giống struct thông thường nhưng các trường không có tên — chỉ có kiểu. Chúng hữu ích khi bạn muốn đặt tên cho toàn bộ tuple và phân biệt nó với các tuple khác có cùng cấu trúc kiểu. Mỗi tuple struct là một kiểu riêng biệt.
1// Tuple structs — same field types but different named types2struct Color(i32, i32, i32);3struct Point(f64, f64, f64);45fn main() {6 let black = Color(0, 0, 0);7 let white = Color(255, 255, 255);8 let origin = Point(0.0, 0.0, 0.0);910 // Access fields by index using dot notation11 println!("R={}, G={}, B={}", black.0, black.1, black.2);12 println!("X={}, Y={}, Z={}", origin.0, origin.1, origin.2);Unit-like structs have no fields. They are useful when you need to implement a trait on a type but have no data to store in the type itself. They behave similarly to the unit type ().
Struct dạng unit là struct không có trường nào. Chúng hữu ích khi bạn cần triển khai một trait trên một kiểu nhưng không có dữ liệu cần lưu trữ trong kiểu đó. Chúng hoạt động tương tự kiểu unit ().
1// A unit-like struct — no fields, no parentheses2struct AlwaysEqual;3struct Marker;45// They can implement traits — a very common use case6impl std::fmt::Display for AlwaysEqual {7 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {8 write!(f, "AlwaysEqual")9 }10}1112fn main() {Methods are similar to functions but are defined within the context of a struct. The first parameter of a method is always self, representing the struct instance the method is called on. Methods are defined within an impl (implementation) block.
Phương thức (method) tương tự hàm nhưng được định nghĩa trong ngữ cảnh của struct. Tham số đầu tiên của phương thức luôn là self, đại diện cho instance của struct mà phương thức được gọi. Phương thức được định nghĩa trong khối impl (implementation).
1#[derive(Debug)]2struct Rectangle {3 width: f64,4 height: f64,5}67impl Rectangle {8 // &self borrows the instance immutably — most common9 fn area(&self) -> f64 {10 self.width * self.height11 }12Rust có tính năng tự động tham chiếu và giải tham chiếu (automatic referencing and dereferencing). Khi bạn gọi rect.area(), Rust tự động thêm &, &mut, hoặc * để khớp với chữ ký phương thức. Đó là lý do bạn không cần viết (&rect).area().
All functions within an impl block are associated functions because they are associated with that type. Associated functions that do not take self as the first parameter (not methods) are often used as constructors. They are called using the Typename:: syntax instead of dot notation.
Tất cả các hàm trong khối impl đều là hàm liên kết (associated functions) vì chúng được liên kết với kiểu đó. Hàm liên kết không lấy self làm tham số đầu tiên (không phải phương thức) thường được dùng làm constructor. Gọi bằng cú pháp Typename:: thay vì dấu chấm.
1#[derive(Debug)]2struct Rectangle {3 width: f64,4 height: f64,5}67impl Rectangle {8 // Associated function (constructor) — no self parameter9 fn new(width: f64, height: f64) -> Self {10 Self { width, height }11 }12Self (viết hoa) là bí danh cho kiểu đang được triển khai trong khối impl. Dùng Self thay vì lặp lại tên kiểu làm cho code dễ bảo trì hơn — nếu bạn đổi tên struct, bạn không cần cập nhật bên trong impl.
Each struct can have multiple impl blocks. This is valid and sometimes useful for organizing code into logical groups. It is especially common when implementing traits (covered in the Generics and Traits chapter).
Mỗi struct có thể có nhiều khối impl. Điều này hợp lệ và đôi khi hữu ích để tổ chức code thành các nhóm logic. Nó đặc biệt phổ biến khi triển khai trait (chủ đề của chương Generics và Traits).
1#[derive(Debug)]2struct Rectangle {3 width: f64,4 height: f64,5}67// First impl block: constructors and basic calculations8impl Rectangle {9 fn new(width: f64, height: f64) -> Self {10 Self { width, height }11 }12Rust does not automatically provide printing for structs. To print a struct using {:?}or {:#?}, you need to opt in to the Debug trait by adding the #[derive(Debug)] attribute before the struct definition.
Rust không cung cấp tự động in struct theo mặc định. Để in struct bằng {:?} hoặc {:#?}, bạn cần opt-in vào Debug trait bằng cách thêm attribute #[derive(Debug)] trước định nghĩa struct.
1#[derive(Debug)] // automatically derives the Debug trait2struct Rectangle {3 width: f64,4 height: f64,5}67#[derive(Debug)]8struct Point {9 x: f64,10 y: f64,11}12- #[derive(Debug)]: This attribute instructs the compiler to automatically generate a Debug trait implementation based on the struct's fields.
#[derive(Debug)]: Attribute này yêu cầu trình biên dịch tự động tạo ra implementation của Debug trait dựa trên các trường của struct.
- {:?}: Compact debug format, suitable for small structs or single-line output.
{:?}: Định dạng debug ngắn gọn, phù hợp cho các struct nhỏ hoặc khi cần in trên một dòng.
- {:#?}: Pretty-printed debug format with indentation, more readable for complex or nested structs.
{:#?}: Định dạng debug đẹp (pretty-printed) với thụt lề, dễ đọc hơn cho struct phức tạp hoặc lồng nhau.
- dbg!: A macro that prints a value to stderr with file and line number, then returns ownership. Very useful for inspecting temporary values during debugging.
dbg!: Macro in giá trị ra stderr kèm tên file và số dòng, sau đó trả lại quyền sở hữu. Rất hữu ích để kiểm tra giá trị tạm thời trong quá trình debug.
#[derive(Debug)] yêu cầu tất cả các trường của struct cũng phải triển khai Debug trait. Hầu hết các kiểu tích hợp sẵn trong Rust đã triển khai Debug, nên điều này thường hoạt động ngay mà không cần thêm gì.
Key Takeaways
Điểm Chính
- Structs group related data into named fieldsStruct nhóm dữ liệu liên quan thành các trường có tên
- impl blocks define methods and associated functions for structsKhối impl định nghĩa phương thức và hàm liên kết cho struct
- Tuple structs have unnamed fields accessed by indexTuple struct có trường không tên, truy cập bằng chỉ mục
- The self parameter determines if a method borrows or consumes the structTham số self xác định phương thức mượn hay tiêu thụ struct
Practice
Test your understanding of this chapter
What is the first parameter of a method in an impl block always named (by convention) and what does it represent?
Tham số đầu tiên của một phương thức trong khối impl luôn được đặt tên gì (theo quy ước) và nó đại diện cho điều gì?
In Rust, you can mark individual struct fields as mutable while leaving other fields immutable.
Trong Rust, bạn có thể đánh dấu từng trường của struct là có thể thay đổi trong khi các trường khác là bất biến.
Call an associated function
Gọi một hàm liên kết (associated function)
struct Circle { radius: f64, } impl Circle { fn new(radius: f64) -> Self { Self { radius } } fn area(&self) -> f64 { std::f64::consts::PI * self.radius * self.radius } } fn main() { let c = Circlenew(5.0); println!("area: {}", c.area()); }
Which syntax is correct for calling an associated function named 'new' on a struct named 'Point'?
Cú pháp nào đúng để gọi hàm liên kết tên 'new' trên struct tên 'Point'?
A struct in Rust can have multiple impl blocks, and this is sometimes useful for organizing methods into logical groups.
Một struct trong Rust có thể có nhiều khối impl, và điều này đôi khi hữu ích để tổ chức phương thức thành các nhóm logic.