So I have this user struct and I want the first user to have the id of 1, second one to have an id of 2, third one to have an id of 3, etc. So the expected output of this code should be: Jack: 1, Jill: 2
Does Anyone know a way to do this or is it just not possible in 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);
}
CodePudding user response:
One way to do this would be to create a global (static) AtomicU32
counter to store the next id
to use and increment it on every call to User::new_user
. Note that the ids will wrap around back to 0 after reaching the max u32 value.
You can read more about atomic types in the docs.
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);
}
Output:
Jack: 1, Jill: 2
CodePudding user response:
Yes, but...
Yes, it's possible. If you're in a hurry, here it is. It involves unsafe
code and hence it's your job to make sure there aren't any data races if your application is multithreaded. But please read on.
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,
}
}
}
However, this is not thread safe. It's not reentrant. It's very difficult to test or mock the ID values. It's brittle and causes a lot of problems. So in addition to the word unsafe
being a bad idea in general, it's also just not good coding style.
What you should do is wrap that global up into another struct.
#[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 }
}
}
Now UserFactory
is a data structure and your caller can determine how local or global it should be. If they want to declare one in main
, that's their business. But if they want three different user databases running in parallel, they can make three UserFactory
instances. It's guaranteed thread-safe, since Rust's borrowing rules ensure only one mutable borrow of a data structure exists at a given moment. And if we really wanted to be thorough (if this is an enterprise application, not a toy environment), we could make a trait
trait UserConstructor {
fn new_user(&mut self, name: String) -> User;
}
and make UserFactory
implement that trait. Then we can even mock it in tests by making a fake UserFactory
that, say, always returns zero or that returns some designated numerical values needed in our tests.
And since traits (excluding dyn
, but we're not using that here) are resolved at compile-time, there's zero overhead to throwing this function in a trait. At runtime, it's still a static early-bound call to a known function.