Home > Back-end >  Is there a way in rust to have a function that returns a value one higher than it did last?
Is there a way in rust to have a function that returns a value one higher than it did last?

Time:04-30

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

Playground

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.

  • Related