Rust – How do I create a global, mutable singleton? (2015)
stackoverflow.com
stackoverflow.com
> 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.
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.
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.
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.
The big problem is the second part - lazy static doesn't allow you to run destructors, and this is one of the few times you really need a custom destructor to tell Rust to kill the CUBLAS/CUDNN pointers at appropriate times.
Ended up connecting it to a struct so that the destructor could be tied to the object rather than anything else, but lazystatic's (lack of) story around destructors definitely created a bunch of problems that weren't apparent until we started finding memory leaks.
Arc<RwLock<T>> fits the bill. Just pass it around. Additional plumbing isn't the end of the world.
(I actually want to write a blog post about this... haven't done so yet though. People reach for static too often and thread_local! too little, IMHO. Of course, not needing either is best.)
That is, too many people skip straight to static without even considering TLS, not that they should be using TLS more often in general.
(I've seen way too many times a thread-local being used as a way to smuggle extra parameters, often through several layers; inevitably, this breaks when one of the intermediate layers decides to run things in a separate thread pool.)
At some point, you see enough of this that you realize the tool is more dangerous than helpful. The makers of Kotlin and many other newer languages seem to know this and have disabled inheritance as a default or left it out entirely.
It's always easy to spot in hindsight, but TBH I think the threshold for creating interfaces or base classes should be a bit higher - likely when you know you will need 3 or more implementations. It's always felt like premature optimization to me, unless something is hard coded to only accept an interface like using Type.IsInterface or something.
Concepts like "Product" are almost always going to have multiple implementations though, so it's easy to judge in that case to make an interface/base class at the beginning. The real magic is guessing what other concepts satisfy this requirement.
This can happen between different layers of app. For example you might have a persistence layer under an API or UI, and want to expose a nice "clean" interface to the API / UI layer.
To add to that, in C++ that approach often saves non-trivial amount of compilation time. Consumers of the object only need to #include the interface, while the implementation of the object, both code and data, stays private and is not needed to compile the consuming code.
That's not a OO problem, that's a software engineering and code quality problem.
Modularity/compartmentalization and the obligation of managing complexity are basic age-old principles. You'll arrive to a big ball of mud if you are oblivious to these basic principles, whether you do inheritance or composition.
Don't pin the blame on a programming paradigm if your developers are clueless about basic software engineering principles.
You can write OO code without inheritance. Inheritance is syntactical sugar, essentially. You can accomplish the same things with interfaces and composition.
The difference between composition and inheritance is that a composed method is fully discoverable. I can see every statement. There's no secret parent method running before or after.
It's not the end of the world, but it's unnecessary and avoidable pain.
A singleton is not a OO pattern, it's a memory access pattern.
It's high time people stop parroting cliches and start to look at patterns as what they are: typical solutions to meet recurring requirements.
A singleton is just a way to ensure a single object is instantiated and exists system-wide. That's it. It's a global object, attached to a negligible (and thus often ignored) requirement to not allow instantiating other objects of the same type.
One way to implement singletons is to get a static function return a static variable. No OO required.
It's all about memory access. Single variable used to store global state. That's it.
Additionally, "memory access pattern" does have an established definition (which you may also be opposed to), and you may confuse people with your usage of it.
The argument is self-evident. A singleton does not use or need any OO construct to be implemented or used.
There is nothing special or specific to OO to exclusively own the concept of ensuring a single globally-accessible system-wide data structure.
As I've said, singletons are implemented with static functions returning static variables. No OO involved at all.
Your only argument tying singletons to OO is the fact they are covered in GoF. However, that argument has no merit not makes any sense. GoF does not specify what is and is not OO. It only shows basic design patterns and presents ways of implementing them with OO languages. Otherwise you would also claim that instantiating variables is OO just because GoF describes factory methods.
Once you do pass context everywhere, it's easy to add new things to it without incurring the cost of adding a new method parameter.
Of course this can be (and has been) abused and you need to use it carefully and as little as possible. I still think it's orders of magnitude better than mutable global variables.
Edit: since a lot of folks are reading this as saying “never” rather than “avoided”. The point here is to keep access to data and state restricted. The entire application does not need to know that some piece of data is global. If you design a central container in a generic manner, it can restrict access to globals in a way that allows for easier refactoring in the future. It also allows one to design users of those globals in a way that doesn’t leak the fact that these things are global.
By doing this, you don’t leak globals through the application, and you keep things isolated in a way that allows for easier testing.
I think the key is that your database stores data that lives outside your application, making it observable outside any particular application instance and allowing you to perform ad-hoc queries to validate consistency and fix problems. If you have such mutable state inside a process, it can be very difficult to debug problems when a series of interactions sends your process into an inconsistent state that is only detected some hours later when (if) it finally crashes.
So, yes, global state is sometimes necessary, but it absolutely should be avoided, and access to it should be restricted.
As far as I know, it doesn't, it's actually thread-local state; you can have multiple states, and you use glXMakeCurrent/eglMakeCurrent to choose which one is active for the current thread.
And the protocol which can be considered the successor of OpenGL (Vulkan) replaced the thread-local state with an explicit reference to the state.
> A database connection is a global.
The product I develop at work uses multiple database connections, to several different databases, which can be added and removed at runtime. They most definitely aren't global.
DB connections as global state, I might go as far as saying global for a single thread, ie it might be reasonable to have a thread-local-storage for checked out connections. I still would keep access to that thread-local restricted, and keep most of the code more testable by having the connection be a parameter to each function.
How do you test that your program behaves the same way with a reference software renderer and the GPU if there’s only one global handle to the video card?
In both cases, how do you add more threads without creating a global lock?
I've seen plenty of cases where this assumption stops being true and only through pain and sweat the design could be disentangled after the fact. On the other hand I've never been more than mildly inconvenienced by in practice global state that had an API that didn't assume it.
Writing the logic as if it could be more than one at a time also makes it much easier to write isolated tests and reduced integration tests.
I have always seen singleton designs in codebases where testing was wanting.
Sure I seen the issues you mentioned too and many other issues where the initial requirements or assumptions changed.
I don't dislike global state because of some lofty goals I memorized back in college and have held up as supreme truth, but because practical experience tells me that it makes my life as a programmer more complicated than it has to be.
You can’t avoid that.
If a language doesn’t offer convenient ways to deal with that state, it simply means that language ain’t a good choice for anything GPU-related, be it graphics or GPGPU.
There is an extremely good response to how to design this in Rust on the SO post. This doesn't change the way that you should design the rest of the code.
The convenience that Rust gives you in this context is strong ownership (i.e. can limit access to the Global except in very desirable patterns) and data-race free with compile time checks around mutability.
The standard library has all of these options available, additionally there are a bunch of libraries from the ecosystem that make it even easier.
It’s desirable when you don’t do much I/O, only implementing some algorithms that compute something.
It’s not desirable at all when the majority of your complexity is I/O. An extreme example is a videogame, these can update global mutable state (the VRAM, abstracted behind GL or D3D) with the bandwidth measured in gigabytes/second, often concurrently so from multiple threads.
Update: and even for computations, when you want performance, rust’s ownership shenanigans often get in the way. See this C++ code for example: https://github.com/Const-me/SimdPrecisionTest/blob/master/rc... You gonna have hard time porting that to Rust, because this vector https://github.com/Const-me/SimdPrecisionTest/blob/master/rc... is updated concurrently by multiple threads of the pool, without any locks.
Looking at the code you're showing, I might model this with deque from the Crossbeam project: https://docs.rs/crossbeam/0.7.3/crossbeam/deque/index.html
I believe that's all lock-free, and offers a safe interface for working with it.
Another thing is usability, and especially readability. OpenMP C++ code is very readable, it’s just a for loop. The example on the page you have linked, on the other hand…
And by the way, when I want memory safety, I don’t code C++, I use C# instead. Much easier to use than Rust, IDE and libraries are awesome, C interop is same as in Rust, and last but not least even very complex software compiles in a few seconds. Despite what most people think it’s very suitable for lower-level system software, including latency sensitive stuff working on slow ARM CPUs, see this for an example: https://github.com/Const-me/Vrmac/tree/master/VrmacVideo
I get how coming from C or C++, deciding that it's not worth turning to a new language in this space that meets the same requirements. I made that same choice years ago when I switched to Java primarily for memory safety. This is no longer a trade-off you need to make with Rust (i.e. turning to a garbage collector), and that's one of the reasons that I've enjoyed it so much. Obviously, YMMV.
I don’t need to make that trade-off either. In the software I’m working on, I often use C# for less performance-critical parts where I want memory safety, and calling C++ DLLs for code which needs to be unsafe for some good reasons.
Apparently, Java had a design goal to force people to code their entire software in Java. C# didn’t, it was designed for native interop from the very beginning, hence value types, real stack, DllImport, unsafe, pointer arithmetic, delegates compatible with C function pointers, HRESULT status codes of exceptions, and so on. At some point I have even made COM interop for Linux: https://github.com/Const-me/ComLightInterop It’s a small project with less than 5k lines of code, because all the heavy lifting was already in the .NET runtime.
Though, you are incurring the cost of C#'s GC for the rest of the program. But, even in that case, if you're using C/C++ then all of that is unsafe and needs to be proved, where as if you used Rust for that FFI, then you'd have less unsafe surface area to prove.
With some care, it's possible to code C# without using GC much, or at all. That Linux h264 video player I've linked above doesn't allocate anything on GC while playback, it's mostly using stack (including stack-allocated spans) and very rarely for some mkv files calls .NET equivalents of malloc/free to allocate on unmanaged heap. Otherwise it probably wouldn't work due to GC issues, I think I only buffer like 150ms of uncompressed audio. Total compressed bitrate can exceed 10 mbit/sec, uncompressed is much more, I didn't expect GC to handle that much data well, especially on raspberry pi.
> all of that is unsafe and needs to be proved
It depends on the project.
If you're working on avionics then yes in theory, but in practice you still have to use C because there's no FAA-certified builds of Rust compiler.
If you're working on a videogame, desktop software, or a mobile app, good engineering and management practices are usually enough. Proving would be nice for them too if it would be free, but not at the cost of development time.
> then you'd have less unsafe surface area to prove
I'm not sure it becomes much less. C interop, and COM interop too, works both ways, one can easily call .NET code from C++ in the same process. I don't usually write C++ code for disk I/O, logging, compression, encryption, serialization, instead I'm passing C#-implemented COM interfaces to native code.