"I have to carefully make sure that each resource has been freed/closed (but not too soon!)"
You can use defer keyword to free right after allocating and it won't be called until the function/program completes. You can use debug or testing allocators to ensure no memory is left unfree'd or double free'd. The std lib has a large selection of custom allocators that you can use depending on your needs. One great one is an "Arena Allocator" which is just a linked-list of allocations that can be free'd at one time, versus tracking all these individual allocations. And if none of these allocators fit your needs, the interface to define one is pretty easy to implement.
https://ziglang.org/documentation/0.7.1/#Choosing-an-Allocat...
"I'm not mutating or holding onto anything I'm not meant to"
Not really sure Zig can help you with this one. With great power comes great responsibility, or something like that.
"that I've accounted for every exception that I can result from a given call"
Zig does not have exceptions, but it does have error types which functions can return. From the function definition you usually can determine all the errors that can possibly be returned, unless the function definer use'd the catch all ! error type. Regardless, it is still much better than regular exceptions because
- you know by the function def that an error could be thrown, the compiler forces the caller to account for a possible error by "unwrapping" the return value
The compiler infers the errors in an error set even if you don't declare it explicitly (e.g. when returning `!void`). This means that you can use a switch statement to have the compiler tell you what cases it contains.
There's also ZLS, a language server implementation for Zig that I believe can help with that (or if not today, it will in the future).
Check out the Zig standard library and the tests for examples on doing some basic stuff with the language. Tests are generally at the end of files, you can search for "test".
The biggest problem with doing CPU-intensive work in Go is not the latency of the GC but rather the lack of maturity of the optimization pipeline compared to GCC and LLVM, the lack of good support for things like SIMD, the relatively poor throughput of the GC, the large overhead of cgo (which matters quite a lot for graphics!), etc.
The reason the Go developers cited is fast compile times, as well as the belief that LLVM is "too big". I don't agree with these: LLVM compile times are fine for ahead-of-time compilation, and LLVM is big because it does important things.
I do have mixed feelings about LLVM for safe GC'd languages, though. LLVM is full of undefined behavior, and its support for precise moving tracing GC is not widely used. So I can definitely sympathize with not wanting to use LLVM for Go, but not for the reasons they cited.
What constitutes 'fine' depends on workflow, size of project, machine horsepower, and personal preference. Some projects require a bit more compile -> experiment -> change -> compile -> experiment -> change than others.
No idea why this is getting downvoted. You'll naturally need more control over memory when building AAA high-performance 3D games, but there's tons of great 2D games made with GC languages (see: Love2D, OpenFL, HaxeFlixel, ImpactJS, Unity).
In a lot of 2D games, simple patterns like object pooling are more than enough to squash any GC problems. There's a whole spectrum of performance requirements out there -- no need to discount a framework due to it's language.
Interestingly enough, many times GC tricks like object pooling aren't even the first concern; it's usually proper utilization of GPU buffers and understanding pixel fill-rate more than anything.
It's truly wonderful time for all programming languages in this space.
Once you get past a certain point of developing a complex game in Unity, one of the optimization techniques is to keep runtime allocations at 0 bytes in order to prevent a GC pause from ever happening. This was a huge pain back when Unity was on Mono 2.10.8 (released Dec 2011) but it's a bit better now.
>Once you get past a certain point of developing a complex game in Unity, one of the optimization techniques is to keep runtime allocations at 0 bytes in order to prevent a GC pause from ever happening.
It is, but it's still an option. It's not like "language with GC" == no-go for games, as the parent implied.
Unfamiliar with any game codebases or much C++, but I know in C we'd do zero-allocation or controlled arena allocation for "embedded" uses. I'd imagine Allocation tuning is always present. Was it much harder in C#?
Yeah that's true but I disagree it's a good idea. Most AAA shops still use non-GC languages because they need the full control/cannot waste ms on random GC pass.
Game dev here. Unless you are making GTA or some high fidelity 3D game. You are wasting your time using a language like C++. You are going to spend more time dealing with memory than actually making your game which is pointless in a 2D game.
Yep. I'm always more curious about how I can get back milliseconds based on 2D rendering techniques and GPU-related concerns. I'm almost never thinking about memory consumption. It's always stunningly low, even for games doing a ton of things.
You'd almost truly have to go out of your way to consume browser-levels of memory.
The current Go is great for writing a 60fps game server. The pauses are very low, and the qualities that make it great for a server also make it great for a game server.
I'm close to implementing hot code update -- in my Go game servers!
For a 2d game engine like this, there's no problem with a GC based language, people write 60fps 2d games in lua/python (which Go easily outperforms), C# and even 3d games like Minecraft in Java.
You can't avoid allocating using the same techniques as in C++. In Go you have to know the intricacies of escape analysis to avoid allocation: objects are heap allocated "by default" and sometimes optimized to be stack allocated. In C++ there is no implicit heap allocation and usually no need for escape analysis.
As a practical example, capturing variables in a closure will usually cause them to be heap allocated in Go, but in C++ capturing has no effect on variable storage.
It seems to me that you're not really taking a charitable interpretation of GP's comment. For example, in my game development experience (granted, I'm 6 years removed from that industry) we often relied on object pools to avoid allocation. You don't need to know anything about Go's escape analysis to use a pool to avoid allocation, as far as I can tell. Could you explain your position further?
Go language constructs allocate in ways that are not immediately obvious. So using object pools is not sufficient to avoid allocation in Go as compared to, say, C++.
In my view (which is the dominant view among compiler engineers for GC'd languages), spending a lot of effort to avoid allocation is a poor use of programmer time in a GC'd language. It is better to just improve the GC to make allocation fast. In a properly designed generational GC like that of Java HotSpot, allocation is about 5 instructions. That is a game changer: allocation is as cheap as a function call plus the prologue.
Unfortunately, Go's designers have so far not deployed generational GC, which is why we keep having these threads about avoiding allocation. (I've seen indications in the last couple of weeks that Go may finally be moving to a generational GC, though, and I hope they do.)
You weren't careful to say it in this subthread though. I had a whole response written up to talk about how you're being completely unreasonable with regards to most gaming projects before I saw this clarification. Now all that effort is wasted! ;)
You’re splitting hairs. Yes, you need to reason about escape analysis. This is pretty easy, especially since the compiler will tell you when things escape. It’s certainly easier than... well, pretty much anything in C++. :)