是的,但是...
是的,这是可能的。如果你很着急,这里有一个解决方案。它涉及到unsafe
代码,因此如果你的应用程序是多线程的,你需要确保没有数据竞争。但请继续阅读。
static mut NEXT_ID: u32 = 0;
struct User {
name: String,
id: u32,
}
impl User {
unsafe fn new_user(name: String) -> User {
let id = NEXT_ID;
NEXT_ID += 1;
User {
name,
id,
}
}
}
然而,这不是线程安全的。它不是可重入的。测试或模拟ID值非常困难。它很脆弱,会引起许多问题。因此,除了单词"unsafe"在一般情况下是一个坏主意之外,它也不是好的编码风格。
你应该做的是将全局变量封装到另一个结构体中。
#[derive(Clone, Default)]
struct UserFactory {
next_id: u32,
}
impl UserFactory {
fn new() -> UserFactory {
UserFactory::default()
}
fn new_user(&mut self, name: String) -> User {
let id = self.next_id;
self.next_id += 1;
User { name, id }
}
}
现在,
UserFactory
是一个数据结构,您的调用者可以确定它是本地的还是全局的。如果他们想在
main
中声明一个,那就是他们的事情。但是,如果他们想要并行运行三个不同的用户数据库,他们可以创建三个
UserFactory
实例。它是线程安全的,因为 Rust 的借用规则确保在给定时刻只存在一个可变的数据结构的借用。如果我们真的想要彻底(如果这是一个企业应用程序而不是玩具环境),我们可以制作一个 trait。
trait UserConstructor {
fn new_user(&mut self, name: String) -> User;
}
让
UserFactory
实现该 trait。这样我们甚至可以通过创建一个假的
UserFactory
来在测试中进行模拟,例如总是返回零或者返回我们测试所需的某些指定数值。
由于 trait(不包括
dyn
,但我们这里没有使用)是在编译时解析的,因此将此函数放入 trait 中不会产生任何开销。在运行时,它仍然是对已知函数的静态早期绑定调用。