In some managed languages, you can define classes with constant members, but obviously they let you write to them during the constructor for the objects. That's the kind of restricted-scope global-state setup phase I am after in an advanced systems programming language. It would be an interesting experiment, anyway.
Once a process has given up rights to open a local file, for instance, it can't get it back. I think that's beautiful.
var myVar MyType
func init() {
myVar = newMyType()
}
func GetMyType() MyType {
return myVar
}
init() is run when the package is imported.myVar is constant (though not guaranteed at the type level).
GetMyType() is the only way to access myVar and return a copy, not a reference.
It's designed for working with interrupts safely rather than threads (so the "initialisation phase" is basically "before enabling interrupts"), but I suppose the same techniques could be used to make something more mainstream.
Singletons can be declared once, and then provided to various systems that need it, with read-only access.
In fact, they're more flexible than that: one can declare multiple nested "scopes" that each have their own initialization phase. For example, a web server could have one scope for process-wide singletons, and another for request-scoped "singletons".
The only thing missing is that these DI systems are for java, which doesn't have the const-correctness you'd ideally want. I'm not aware of any equivalent (and widely-used) systems for languages like c++ or Rust.
(Yes, I can feel hundreds of HN readers rolling their eyes as they read this, given the bad rep. DI systems have, but they're widely popular among FANG companies for a reason.)
It's not a program-wide stage, but it's sufficient to use it in `main()` to make it behave like that.
> And it's totally safe, except you could never convince a language like Rust that it is.
I think you should be able to create a safe abstraction around that in Rust.
> The Deref implementation uses a hidden static variable that is guarded by an atomic check on each access.
That's totally unnecessary in the scenarios I'm talking about, but it's needed to make Rust happy. Also, it's not totally clear to me that I can feed a lazy_static information derived from main() like command-line arguments, though I did not try too hard to figure that part out. I just wound up plumbing arguments through all my functions.
https://docs.rs/once_cell/1.4.0/once_cell/unsync/struct.Once...
Assuming you're dead set against thread local storage.
Imagine a Rust where a static variable (not mut) could be modified in `main` in a type of unsafe {} block. Then, access to those statics in the rest of the program, even across threads should be safe and incur no runtime checks. That would be similar to what I'd like to try.
The unsync type you linked says it isn't thread-safe. If you convert it to a sync type, then it has extra runtime checks. Both sync and unsync make you unwrap Option<T>'s to get the values, which again would not be necessary if I could communicate to the compiler that I am going to set up some global constants, and they will all be ready by the end of some lexical block.
Just thinking out loud.
use std::cell::UnsafeCell;
use std::mem::MaybeUninit;
use std::ops;
pub struct InitializeCell<T> {
inner: UnsafeCell<MaybeUninit<T>>,
}
unsafe impl<T> Sync for InitializeCell<T> {}
impl<T> InitializeCell<T> {
pub const unsafe fn new_uninitialized() -> InitializeCell<T> {
InitializeCell {
inner: UnsafeCell::new(MaybeUninit::uninit()),
}
}
pub const fn new(init: T) -> InitializeCell<T> {
InitializeCell {
inner: UnsafeCell::new(MaybeUninit::new(init)),
}
}
pub unsafe fn init(&self, init: T) {
(*self.inner.get()) = MaybeUninit::new(init);
}
}
impl<T> ops::Deref for InitializeCell<T> {
type Target = T;
fn deref(&self) -> &T {
unsafe {
&*(*self.inner.get()).as_ptr()
}
}
}
static VALUE: InitializeCell<i32> = unsafe { InitializeCell::new_uninitialized() };
static OTHER_VALUE: InitializeCell<i32> = InitializeCell::new(6);
fn main() {
unsafe {
VALUE.init(5);
}
println!("{}", *VALUE);
println!("{}", *OTHER_VALUE);
}When I tried to do this a couple years ago (with mem::uninitialized), I failed to get something usable. I think I was also concerned that having to use unsafe for the reads would prohibit compiler optimizations, even if it could be wrapped in a safe wrapper as you did here. Maybe that was worrying for nothing. Maybe it's time to try Rust again.