在Rust中没有像C++或Java那样的传统构造函数,但通常使用关联函数(通常命名为new)和所谓的工厂方法来创建类型对象。这与语言的历史有关,该语言特别关注安全性和初始化的明确性:只有显式编写和调用的函数才负责正确初始化结构的每一个字段。
问题的历史
最初,Rust中的结构初始化允许直接赋值所有字段(即所谓的"struct literal"语法)。然而,为了确保不变性,隐藏细节并实现额外的检查,采用工厂方法(impl SomeStruct { fn new(...) -> Self { ... } })或通过模式的泛化(builder pattern)。
问题
主要任务是防止部分初始化的对象,并使得不可能使用处于无效状态的结构。这对于复杂结构(例如与资源相关的——文件、套接字等)尤其重要,其中手动初始化所有字段容易出错。
解决方案
在Rust中,建议创建工厂方法,这些方法返回完全初始化的对象,必要时执行验证并隐藏实例化的细节。
代码示例:
struct User { username: String, age: u8, } impl User { pub fn new(username: String, age: u8) -> Option<Self> { if age >= 18 { Some(Self { username, age }) } else { None } } } fn main() { let user = User::new("Alice".to_string(), 20); // user: Option<User>, 安全处理错误 }
关键特性:
fn new)。可以在结构中创建私有字段,以便无法直接在模块外创建实例吗?
可以,如果使结构的所有字段为私有并且只提供公共工厂方法,则无法在模块外直接初始化结构。
工厂方法必须始终命名为new吗?
不,这是约定,但不是强制性的。对于不同的初始化策略,可以使用如"with_capacity"、"from_config"、"from_env"等名称。
可以有私有构造函数吗?
可以,如果关联函数声明为fn new(...) -> Self没有pub修饰符,则无法在该模块外调用它。这允许实现单例、强制工厂或隐藏初始化。
直接使用具有开放字段的结构,没有工厂方法:
struct Connection { fd: i32, timeout: u64, } let c = Connection { fd: -1, timeout: 10 }; // fd: -1对于描述符无效!
优点:
缺点:
使用私有字段和工厂方法:
pub struct Connection { fd: i32, timeout: u64, } impl Connection { pub fn new(fd: i32, timeout: u64) -> Option<Self> { if fd >= 0 { Some(Self { fd, timeout }) } else { None } } }
优点:
缺点: