
Rust – How do I create a global, mutable singleton? (2015) - gitgud
https://stackoverflow.com/questions/27791532/how-do-i-create-a-global-mutable-singleton
======
richard_todd
I would love to play with a programming language that has a notion of an
initialization phase, where constraints are relaxed. So often, especially in
cli programs, you just want to set up some ambient state based on arguments or
whatever that will be constant after you set it up. After setup, then it might
be read by multiple threads, etc, and it's totally safe, except you could
never convince a language like Rust that it is. Even a series of one-time
copies into global "constants" would be sufficient to do the kinds of things
I'm thinking about... require it to happen from `main` and (harder to do...)
maybe don't let the user create threads until the switch is flipped. But don't
put run-time checks on access or whatever... make it truly global and constant
once initialization is over.

~~~
pradn
The observation that programs have distinct init and main phases is one reason
why capability-based permissions exist. You can do initial work - read a
config file, open ports - and then drop the capabilities to do those things.
This way, a buffer-overflow that occurs after the init phase ends up being
less harmful. The program no longer has the capabilities to open new files or
new ports or cause more havoc.

~~~
kstrauser
I really like the OpenBSD "pledge" API
([https://man.openbsd.org/pledge.2](https://man.openbsd.org/pledge.2)). At any
point, a program can promise that it will only make specific syscalls from
then on, and subsequent pledges can only further restrict that list of
syscalls.

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.

------
eden_h
Ran into this issue with a crate that uses CUDA in Rust - how do you hang onto
the pointer to CUBLAS/CUDNN so that all CUDA functions use the same pointers
and _also_ it goes out of scope at the end of execution.

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.

------
echelon
Stop caring about OO patterns. Inheritance and the stuff that came with it
during that era was a boondoggle.

Arc<RwLock<T>> fits the bill. Just pass it around. Additional plumbing isn't
the end of the world.

~~~
betenoire
Inheritance and the stuff that came with it isn't the end of the world.

~~~
smt88
I have inherited old Java, PHP, and C# code bases with more than a thousand
classes, lots of inheritance, and no composition. It can be so difficult to
answer the question, "What happens when I call this method on this instance?"
that a 10-min edit takes a full day. The code is basically frozen for fear of
breaking something

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.

~~~
vsareto
I've seen C# interfaces and base classes that were committed years ago and
never got a second implementation. This essentially makes the interface and
base class a pointless ritual in hindsight.

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.

~~~
rickmode
I had this same belief but there is one other useful situation where an
interface plus just one implementation class helps, and that's when you want
to hide the noise of implementation details from another part of the program.

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.

~~~
Const-me
Yes.

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.

------
jupp0r
One much better solution than using any globals is passing an opaque context
object through the call chain, much in the way many Go libraries do it. This
allows you to scope what APIs have access to what parts of the context and
prevents its implementation details to be splattered all over the place. It
also allows for mocking parts of the runtime in unit tests and makes
synchronization across threads an implementation detail of the APIs that
downcast the context (as opposed to Arc<RwLock<T>>, which is not exactly a
pleasure to use).

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.

------
bluejekyll
Don’t, is always the simplest answer. Global state that is non-constant,
causes all sorts of testing issues down the line. It should be avoided.

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.

~~~
identity0
I hate when I ask something about globals on SO and I get the same blanket
answer. Why does every programmer feel the need to tell me that "globals make
it hard to reason about the state of your program?" Unfortunately most systems
are global based. OpenGL has one global state. A database connection is a
global. Either you can use globals to model these things properly or you can
complicate it by not using globals. The library will be using globals under
the hood anyways.

~~~
logicslave
Its dogma. Like a religion. A whole generation of people believing in things
with no root evidence except that the knowledge is ubiquitous

~~~
bluejekyll
I have plenty of history with global state, various contexts in large
applications, that make testing code unnecessarily complex.

So, yes, global state is sometimes necessary, but it absolutely should be
avoided, and access to it should be restricted.

------
pornel
Rust doesn't accept "but my program is single-threaded!" as an answer. The
language doesn't believe you. Every global has to be thread-safe, or marked as
`unsafe` to use.

~~~
Rusky
The "my program is single-threaded" button is thread-local storage. If your
program truly is single-threaded, then TLS is the same thing as a global!

------
luizfelberti
Kinda off-topic, but can anyone explain to me why a good answer with 100+
upvotes is burried behind a "me too" answer with a score of -2, and why does
StackOverflow consistently do this?

~~~
mekoka
There's a setting to order answers based on votes in the page.

------
gutino
A legit singletons/global states use case are for WebAssembly bindings. So
stop trolling you purists.

~~~
wnoise
Can you explain how and why it's necessary?

