0

I have a simple example game entity called a Nation, which is a struct that contains all the data relevant to the player's assets and activities. The player is allowed to change their nation name, which is used to log in and take their turns. Thus, the player has to have a unique identifier that doesn't change.

There are also other things in the game system which similarly need unique identifiers, so to my dinosaur C brain, the obvious solution was to try making a global HashMap to contain the struct name as a key, and the instance count... and then auto-increment that counter whenever a new struct is created, using the current value as the ID.

Rust really hates me for trying that, which is fine. But, I'd like to learn a more proper way to accomplish this. Since I have to implement Default for these anyways, putting the increment and then assignment in the default() function seems like the right place to use it, but the counters themselves need to live somewhere accessible to those functions.

Right now, I have this, which is ugly but seems to work, so far:

static mut nation_id : i64 = 0;

#[derive(Debug)]
struct Nation {
    id          : i64,          // unique naiton ID, established at allocation
    name        : String,       // the nation name, entered at runtime
    // more stuff here...
}

impl Default for Nation {
    fn default() -> Nation {
        unsafe {
            nation_id += 1;
            Nation {
                id: nation_id,          // We want this to actually come from a counter
                name: String::from(""),
                // more stuff here...
            }
        }
    }
}

// World just holds a Vec<Nation> that is initialized to new() and thus empty.

fn main() {
    println!("This is just a test.");
    let mut w : World = Default::default();
    println!("{:?}", w);
    println!("{} nations exist.", w.nations.len());
    let mut n : Nation = Default::default();
    println!("{:?}", n);
    w.nations.push(n);
    println!("{} nations exist.", w.nations.len());
    let mut n2 : Nation = Default::default();
    println!("{:?}", n2);
    w.nations.push(n2);
    println!("{} nations exist.", w.nations.len());
    println!("Test completed.");
}
1
  • Probably best to use a random unique id like a UUID, unless they must be incremental. Otherwise, use an AtomicI64 to avoid unsafe. Commented Dec 14, 2022 at 19:50

1 Answer 1

2

If you want a counter, one way to do that is to use atomics. This function will return a counter and can work on multiple threads concurrently.

use std::sync::atomic::AtomicU64;
use std::sync::atomic::Ordering::SeqCst;

pub fn unique_id() -> u64 {
    static COUNTER: AtomicU64 = AtomicU64::new(0);
    
    COUNTER.fetch_add(1, SeqCst)
}

We can also improve it by enforcing that ids must be unique.

pub fn unique_id() -> u64 {
    static COUNTER: AtomicU64 = AtomicU64::new(0);
    
    let id = COUNTER.fetch_add(1, SeqCst);
    assert_ne!(id, u64::MAX, "ID counter has overflowed and is no longer unique");
    id
}

Rust Playground

Sign up to request clarification or add additional context in comments.

1 Comment

Thank you! This neatly solves the problem and mimics what I'd use if it were shifted into SQL at some point as well.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.