Creating objects with an incrementing index

I'm trying to create a bunch of structs and have each know its place in the order of creation but struggling to do so without an unsafe block. I'm learning (very early in the process) so want to learn how to avoid unsafe code.

struct LogMonitor {
  logfile:  String,  
  index: usize,
}

static mut next_monitor: usize = 0;

impl LogMonitor {
  pub fn new(f: String) -> LogMonitor {
    let index = next_monitor;
    next_monitor += 1;
    LogMonitor {
      logfile: f,
      index,
    }
  }
}

The above gives an error at let index = next_monitor; which says use of static requires an unsafe function or block, because it could be mutated in multiple threads.

I then tried thread_local as below but can't get this to work either. I can't make NEXTMON mutable, so the *i = *i + 1; is an error.

I'm very new to Rust so any help is very userful. Thanks.

thread_local! {
  static NEXTMON: usize = 0;
}

impl LogMonitor {
   pub fn new(f: String) -> LogMonitor {
    NEXTMON.with(|i| {
      let index = i;
      *i = *i + 1;
      index;
    });

    LogMonitor {
      logfile: f,
      index: 1
    }
  }
}

Whats a good way to do this without writing unsafe code?

I would suggest using AtomicUsize and fetch_add(1, Ordering::Relaxed), which returns the previous value.

2 Likes

You need some kind of shared mutability. Since the object in question is simply an integer, I suggest making it an AtomicUsize. Like this:

#[derive(Debug)] // To see what we've created.
struct LogMonitor {
  logfile:  String,  
  index: usize,
}

use std::sync::atomic::{AtomicUsize, Ordering};
static NEXT_MONITOR: AtomicUsize = AtomicUsize::new(0);

impl LogMonitor {
  pub fn new(f: String) -> LogMonitor {
    // For atomics, there is a single operation for a kinda-"postincrement".
    let index = NEXT_MONITOR.fetch_add(1, Ordering::Relaxed);
    LogMonitor {
      logfile: f,
      index,
    }
  }
}

// Check if it really works.
fn main() {
    println!("{:?}", LogMonitor::new("first".into()));
    println!("{:?}", LogMonitor::new("second".into()));
    println!("{:?}", LogMonitor::new("third".into()));
}

Playground

2 Likes

Awesome, my code runs... finally. Thanks for taking the time to write it out, that's very helpful.

Thanks everyone who responded here and on the earlier topic.

For reference, with a thread-local counter, you can use Cell or RefCell for mutability:

pub fn new(logfile: String) -> LogMonitor {
    thread_local! {
        static NEXTMON: Cell<usize> = Cell::new(0);
    }
    let index = NEXTMON.with(|i| i.replace(i.get() + 1));
    
    LogMonitor { logfile, index }
}

(Playground)

2 Likes

Don't use static mut ever. It's a syntax to allow machine-translate existing C code written without multithreading in mind. If you ever write it with your hand, it's either you're machine-translating by hand or you have some misunderstanding on the language semantics.

I've seen someone argued it's ok because they're run it on cpu with single core. This cannot justify usage of static mut. Even on code without threading at all, you can create two &mut T from it within the different portion of the same call stack, which is insta-UB. And it's mostly not possible to isolate it locally.

2 Likes

This seems a little harsh when std still doesn’t have a way to produce a static Mutex<_> without it, as far as I can tell. Did the authors of the std::Once docs misunderstand the language semantics?

Because at the time the docs are written we haven't had the ::lazy_static, ::once_cell, or the ::parking_lot. The former two gives you a safe way to lazily initialize global variable and the last one gives an alternative implementation of mutex which allows const initialization. All three are popular amd generally trusted by community.

I won’t dispute that they’re the right choice for most projects, but there’s always a future-compatibility hazard when using 3rd-party libraries, however slight. Not everyone uses the cargo ecosystem to write Rust code, and so I bristle a bit at proscriptions against the only way to get things done without it— All I ask is that you leave some room for nuanced or unusual situations in your rhetoric.

For my purposes (a useful but non-mission critical app being written largely for learning) I have a solution, but the last few comments have left me realising that there's a lot more to this than I currently appreciate. So I think it would be good to have a thorough discussion of the issues being raised here to clarify the options with their pros and cons.

Thanks again everyone.

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.