Constructor
Rust không có constructors như các ngôn ngữ hướng đối tượng khác (Java, C++, etc.). Thay vào đó, Rust sử dụng convention là tạo Associated Functions có tên new để khởi tạo instance mới.
Convention cơ bản: new()
struct Point { x: i32, y: i32, } impl Point { fn new(x: i32, y: i32) -> Point { Point { x, y } } } fn main() { let point = Point::new(1, 2); println!("({}, {})", point.x, point.y); }
Tại sao không dùng pub cho fields?
Trong thực tế, chúng ta thường giữ các fields là private và cung cấp constructor new() để kiểm soát việc khởi tạo:
pub struct Rectangle { width: u32, height: u32, } impl Rectangle { // Constructor đảm bảo width và height hợp lệ pub fn new(width: u32, height: u32) -> Result<Self, String> { if width == 0 || height == 0 { return Err("Width and height must be positive".to_string()); } Ok(Rectangle { width, height }) } pub fn area(&self) -> u32 { self.width * self.height } } fn main() { let rect = Rectangle::new(10, 20).unwrap(); println!("Area: {}", rect.area()); // Lỗi compile: fields are private // println!("{}", rect.width); }
Multiple Constructors
Rust cho phép tạo nhiều constructor methods với tên khác nhau:
pub struct Color { r: u8, g: u8, b: u8, } impl Color { pub fn new(r: u8, g: u8, b: u8) -> Self { Color { r, g, b } } pub fn from_hex(hex: u32) -> Self { Color { r: ((hex >> 16) & 0xFF) as u8, g: ((hex >> 8) & 0xFF) as u8, b: (hex & 0xFF) as u8, } } pub fn black() -> Self { Color { r: 0, g: 0, b: 0 } } pub fn white() -> Self { Color { r: 255, g: 255, b: 255 } } } fn main() { let c1 = Color::new(255, 0, 0); // Red let c2 = Color::from_hex(0x00FF00); // Green let c3 = Color::black(); // Black let c4 = Color::white(); // White }
Builder Pattern cho complex constructors
Khi struct có nhiều parameters, nên sử dụng Builder Pattern:
pub struct User { username: String, email: String, age: Option<u32>, country: Option<String>, } pub struct UserBuilder { username: String, email: String, age: Option<u32>, country: Option<String>, } impl User { pub fn builder(username: String, email: String) -> UserBuilder { UserBuilder { username, email, age: None, country: None, } } } impl UserBuilder { pub fn age(mut self, age: u32) -> Self { self.age = Some(age); self } pub fn country(mut self, country: String) -> Self { self.country = Some(country); self } pub fn build(self) -> User { User { username: self.username, email: self.email, age: self.age, country: self.country, } } } fn main() { let user = User::builder( "duyet".to_string(), "duyet@example.com".to_string() ) .age(25) .country("Vietnam".to_string()) .build(); }
Constructor với validation
pub struct Email { address: String, } impl Email { pub fn new(address: String) -> Result<Self, &'static str> { if !address.contains('@') { return Err("Invalid email address"); } Ok(Email { address }) } pub fn as_str(&self) -> &str { &self.address } } fn main() { match Email::new("user@example.com".to_string()) { Ok(email) => println!("Valid email: {}", email.as_str()), Err(e) => println!("Error: {}", e), } }
Khi nào dùng new() vs default()?
new(): Khi cần parameters để khởi tạodefault(): Khi có thể tạo instance với giá trị mặc định hợp lý
Thông thường, struct sẽ implement cả hai:
#[derive(Debug)] pub struct Config { pub host: String, pub port: u16, } impl Config { pub fn new(host: String, port: u16) -> Self { Config { host, port } } } impl Default for Config { fn default() -> Self { Config { host: "localhost".to_string(), port: 8080, } } } fn main() { let config1 = Config::new("example.com".to_string(), 3000); let config2 = Config::default(); println!("{:?}", config1); println!("{:?}", config2); }
The Default Trait (Default Constructors)
Rust hỗ trợ default constructor thông qua Default trait.
struct Point { x: i32, y: i32, } impl Default for Point { fn default() -> Self { Point { x: 0, y: 0 } } } fn main() { let point = Point::default(); println!("({}, {})", point.x, point.y); }
Default còn có thể sử dụng như một derive nếu tất cả các field của struct implement Default.
#[derive(Default)] struct Point { x: i32, y: i32, } fn main() { let point = Point::default(); println!("({}, {})", point.x, point.y); }
Thường với một struct cơ bản chúng ta thường sẽ phải cần cả new và Default method.
Một ưu điểm của Default là struct hoặc enum của chúng ta có thể được sử dụng một cách
generic trong các trường hợp như dưới đây hoặc cho tất cả các *or_default() functions
trong standard library.
#![allow(unused)] fn main() { fn create<T: Default>() -> T { T::default() } }