Comment by packetlost

17 hours ago

> it has no hidden memory allocation or control flow

Rust had like 3 allocating types total. If you aren't working with extremely deeply nested 3rd party types it's trivial to identify when allocations happen. Hell you could throw a lint rule together in like 5 minutes to warn on it if you're really worried. Besides Drop (excluding async) is there even any hidden control flow?

> Im speculating here, but I believe its advantage over Rust for that specific application is that you have tighter control over exactly when memory is allocated and deallocated, and how data is laid out in it.

Rust has almost exactly the same semantics for controlling allocations and deallocations, it just prevents you from screwing it up and not freeing something or using the allocation after freeing it. You still have to pass around your reference in your call stack until you no longer need it.

> Rust wants to tie allocation lifetimes to scope in a very fine grained way that I would guess is beneficial the vast majority of the time, but does still make it harder to reason about when you’re about to stall out the CPU while the allocator does its thing.

It's really not substantially different. You allocate ahead of time or don't allocate at all. The only real difference is you might want to use an Option instead of an uninitialized pointer because it's semantically more correct and harder to screw up.

But here I feel like we’re at risk of heading down the same old doom spiral that plagues any conversation about programming languages when people try to treat it as a competition: getting pedantic about what’s technically possible in a language. It’s much more interesting to talk about how a language wants to be used.

So, in the case of Zig, every function that wants to be able to allocate or deallocate heap memory needs an explicit reference to an allocator. That means that you can tell whether a function might allocate memory from its signature. It also means that changing a function so that it can allocate is explicitly a breaking change.

That’s a really interesting design decision. And the reasons why someone would or would not want something like that baked directly into the language are so much more interesting than bickering about how technically with proper discipline you can have that kind of control in any non-GC language.

  • > That means that you can tell whether a function might allocate memory from its signature.

    In practice that's not the case, as many objects own a reference to their allocator. It's still explicit, but it might be hidden in the signature, especially if you have some sort of interface that can take an allocating and a nonallocating data structure alike.

  • I agree with you, but it’s not baked directly into the language? It’s just a convention and a shared trait?

    • Not original commenter but the reality with zig is a little in between being simply convention vs. a language requirement. Is it a language requirement? no.

      However, there's no global allocator in zig. You simply cannot call the language's equivalent of malloc() because it doesn't exist - at least not as a global symbol. That leaves you with three choices: 1) Define a global allocator; is a valid choice and would make a zig program more like C, C++ or Rust in terms of not having to think about scope-level allocation patterns 2) Pass an allocator into that scope (this is the community convention) 3) Create/instantiate an allocator itself inside that scope

      (1) would be valid, though may not be idiomatic; global allocator like malloc becomes an opt-in

      (2) Expensive and inefficient for most scopes, though not all.

      (3) cheap, idiomatic but potential for noise/boilerplate

      2 replies →

> Rust had like 3 allocating types total.

Seriously? Categories of types maybe, but literal types it's more than that.

Even closures allocate if they need to capture their environment.

  • It’s actually the opposite: the language has zero allocations in it. Allocation is entirely a library concern.

    When you want to have a closure allocate an environment, the closure itself does not: the Box you wrap it in, which is a stdlib type, does.