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.
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.
I really like the OpenBSD "pledge" API (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.
Yes, or on a more primitive level how traditional UNIX lets a process drop setuid-root permissions once you've done what you need to do with them.
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.
That's not exactly what you mean, but you could do this in Go by using the `init()` function. From there you assign a package level variable to either an object that only exposes public getters, or one that is only exposed/manipulated by public functions.
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.
Dependency injection systems (like Dagger or Guice) are essentially programming languages that specifically model this kind of initialization-vs-runtime distinction. Dagger is actually implemented as a compiler extension.
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.)
Rust has https://lib.rs/once_cell for this. It's a wrapper type that ensures something can be written to only once, atomically, and after that it's immutable, so it can be shared freely.
It's not a program-wide stage, but it's sufficient to use it in `main()` to make it behave like that.
Yeah, I've seen lazy_static. From the documenation [1]:
> 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.
It's not about "dead set," it's just a thought experiment. Setting global constant data at the beginning of a program, before "the real work" starts, is totally safe to do, but there's not a language I'm aware of that lets me explain this to the compiler. Either you get a C-style free-for-all, a Rust-style series of road-blocks, or a managed-language with runtime overhead.
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.
That's actually really nice. It does appear that Rust will peel all the layers away when opt-level is 2 or higher. I could live with that. You can (re)set the value from anywhere, but at least it has to be marked unsafe. Fair enough!
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.
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.
Why is Arc needed? It's a global singleton so we don't need to worry about dropping it. Just a RwLock<T> should be fine or maybe even Cell if you don't need RwLock guarantees.
Arc is there if you need to send that singleton between threads. If your program is a single-thread non-async one, you can drop both Arc and Mutex, leaving you with just a regular data field :)
If you really need something that's only for a single thread, then using TLS is gonna be better than a static.
(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.)
TLS has many of the same downsides of most global state. It does remove the concurrent access issues (as long as it is only used inside a single thread), but it still creates a testing problem if access to it is not restricted. That is, don’t make globals publicly accessible, just like you wouldn’t make all data in an object publicly accessible.
Absolutely, I'm not saying that people should be reaching for global state more, I'm saying that if you really need global state, reaching for TLS over a static is often the right call, because you're minimizing the global-ness, which drops some of the type system restrictions.
That is, too many people skip straight to static without even considering TLS, not that they should be using TLS more often in general.
TLS has the additional downside that it makes it harder to move part of the processing to another thread, because the other thread will have a different TLS state. That is, once you have TLS, x() and thread::spawn(x).join() might no longer do the same thing. Using TLS creates thread affinity when one might not be expecting it.
(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.)
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.
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.
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.
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.
> 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
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.
> 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.
Inheritance is the stuff that I dread when reading code. A class that inherits from another class (which itself isn't a base type) looks to me like the entrance of a labyrinth, or a reminder that it's time to go get a coffee.
It's not the end of the world, but it's unnecessary and avoidable pain.
In most cases, additional plumbing is not even necessary, as it's often tied to a single object instantiated in the main function (database client, connection pool, config map, ...). The Arc<RwLock<T>> would go in that object.
In the spirit of correcting people, a singleton is absolutely an OO pattern, which doesn't mean it can't also be a "memory access pattern" (in some sick twisted meaning of memory access pattern). I mean, it was brought to fame by the GoF OO design book.
No, a singleton is not a OO pattern. At all. Quoting GoF does nothing to refute that fact.
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.
I appreciate your response. Unfortunately, I don't feel you have presented a convincing argument against its established classification as an OO design pattern for the last several decades.
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.
> I appreciate your response. Unfortunately, I don't feel you have presented a convincing argument against its established classification as an OO design pattern for the last several decades.
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.
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.
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.
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.
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.
Because that's the academic answer that doesn't have to deal with real world constraints and pressures. I too have a deep hatred of the "you don't want to do that" answer for this reason: I do deal with real world constraints and pressures and I'm asking my question because "the right way" can't be done in my circumstances!
To quote myself, “it should be avoided”, doesn’t mean never.
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.
I disagree with that in general (but certainly won't argue against certain very specific use cases). Being diligent about testing has made me loathe global objects; it's so much easier to write a test for a function that makes a database query when I can pass in a mock database object, rather than having to patch the environment to make that same function use a global mock. If you can get to the point that almost all tested functions operate only on things passed into them as arguments, some previously difficult things can become almost trivial.
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.
What happens if you need to use your code to migrate from one database to another, but all the low level logic uses a single global database connection?
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?
There are cases where you only have 1 instance of something, never 2 or more. Most developers use such things dailyu, like in JS you have the window object, most of the time you don't pass the window as a parameter in all your functions but in one project I worked it was a requirement to pass the document as a param in all functions that needed it. So the my opinion is that it depends, sometimes having a World,App,Window etc global helps more then it causes issues and is preferable to a solution that is pure that costs you more. FYI I use a functional style as much as possible, no side effects but sometimes pragmatism wins, in my current project I guarantee we won't change our database or upgrade the current framework to a new cooler one.
> There are cases where you only have 1 instance of something, never 2 or more.
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.
As I said it depends, do you pass in js the Math object around because you assume that in future you want to replace the Math.abs function with something different ?
Sure I seen the issues you mentioned too and many other issues where the initial requirements or assumptions changed.
Databases are also designed for safe concurrent access with enforceable constraints on data. Would you like your application behaviour to depend on a transient "database" someone made up on the fly? :)
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.
A database connection perhaps, but different connections to the same database will see different states of the database depending on the isolation level of the current transaction.
This is very nearly the exact advice I would give a person working on my team. But I also think when the question is asked broadly it can be ... Presumptuous. Even though I've come to the same conclusions from my experience just discarding the premise of the question (without answering it especially) won't sit will.
The OP’s question was about OpenGL. GPUs have gigabytes of external mutable state. Specifically, in my PC the figure is “11GB of that state”.
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.
> can limit access to the Global except in very desirable patterns
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.
You're comparing an implicitly unsafe language implementation, C++, with a language that guarantees safety by default, Rust. There are many people working on non-blocking lock-free datastructures in Rust, but this often requires unsafe implementations. Essentially removing the safety restrictions to do things like what you're showing in the C++.
I have no doubts it’s possible to replicate the functionality in Rust. Most languages are Turing-complete, after all. What I have doubts about is performance parts, i.e. replicating the functionality without moving data from a single std::vector into randomly-allocated heap objects, or using synchronization primitives.
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
Readability is probably always going to be a very personal thing as it has a lot to do with familiarity. Having worked with all the languages you mention, I don't find Rust to be any less readable, and definitely enjoy reading it more than C or C++.
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.
> This is no longer a trade-off you need to make with Rust
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.
That is a great point about C#, C FFI is very simple as compared to Java.
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.
> incurring the cost of C#'s GC for the rest of the program
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.
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.
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?