在 Rust 中有一种方法可以编写一个函数,使其返回值比上次高出1吗?

4

我有一个用户结构体,我希望第一个用户的ID为1,第二个用户的ID为2,第三个用户的ID为3,以此类推。所以这段代码的期望输出应该是:Jack: 1,Jill: 2。
有人知道如何实现吗?或者这在Rust中根本不可能吗?

struct User {
    name: String,
    id: u32,
}
impl User {
    fn new_user(name: String) -> User{
        User{
            name,
            id,
        }
    }
}
fn main() {
    let user1 = User::new_user("Jack".to_string());
    let user2 = User::new_user("Jill".to_string());
    println!("{}: {}, {}: {}", user1.name, user1.id, user2.name, user2.id);
}
2个回答

6

一种方法是创建一个全局(静态)AtomicU32计数器来存储下一个要使用的id,并在调用每个User::new_user时将其递增。请注意,当达到最大u32值后,id将回绕回0。

您可以在文档中阅读有关原子类型的更多信息。

use std::sync::atomic::{AtomicU32, Ordering};

struct User {
    name: String,
    id: u32,
}

static NEXT_ID: AtomicU32 = AtomicU32::new(1);

impl User {
    fn new_user(name: String) -> User {
        User {
            name,
            // we increment the value by 1 and fetch the old value
            // see also: https://doc.rust-lang.org/std/sync/atomic/enum.Ordering.html
            id: NEXT_ID.fetch_add(1, Ordering::Relaxed),
        }
    }
}

fn main() {
    let user1 = User::new_user("Jack".to_string());
    let user2 = User::new_user("Jill".to_string());
    println!("{}: {}, {}: {}", user1.name, user1.id, user2.name, user2.id);
}

输出:

Jack: 1, Jill: 2

游乐场


我会在函数内部定义计数器。 - Chayim Friedman

3

是的,但是...

是的,这是可能的。如果你很着急,这里有一个解决方案。它涉及到unsafe代码,因此如果你的应用程序是多线程的,你需要确保没有数据竞争。但请继续阅读。

static mut NEXT_ID: u32 = 0;

struct User {
  name: String,
  id: u32,
}

impl User {
  // WARN: Not at all thread-safe!
  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()
  }
  // Assuming the definition of struct User from above...
  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 中不会产生任何开销。在运行时,它仍然是对已知函数的静态早期绑定调用。

永远不要使用static mut,这太危险了。最好使用原始的UnsafeCell(或者夜间版的SyncUnsafeCell)。或者更好的方法是使用原子操作或线程本地的Cell - Chayim Friedman

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接