What Should We _Remove_ from Zig?

fn callFunction(func: anytype, args: anytype)
@typeInfo(@TypeOf(func)).@"fn".return_type.? {

Nah it should be:

fn callFunction(func: anytype, args: std.meta.ArgsTuple(@TypeOf(func))) @TypeOf(@call(.auto, func, args)) {
3 Likes

It also does not have to be personal project. Some applications are fast enough with using append and doing a lot of (re)allocating memory in 60fps. Not everything has to be optimised to the maximum. Some stuff just needs to be simple both for maintainability and readability.

For example, if you introduce error handling everywhere, you are gonna have hard time refactoring it. If you preallocate everything and never use append, how much time are you actually optimizing? You have to profile it first, see the bottlenecks and act upon it. Even then your application may not even need optimizing. If its doing what it needs to in a reasonable amount of time, why even bother?

There are so many ways to optimize code and handle errors, so why remove very convinient utilities? Improving by omission can be good sometimes but it has to come from real world examples and very careful thinking. I don’t believe that removing append, .? or catching errors with if will make your code drastically better in those aspects.

3 Likes

I think the case of “preallocate everything and never use append” is more about avoiding memory leaks or uninitialized data than it is performance. See Reserve First

2 Likes

For what it’s worth, I love this while loop iterator. Not really any great reasons for that, but I don’t find it harmful or deserving of removal like some of the other suggestions here.

1 Like

I see the point, and agree with the pitfall there. I still think removing a common case function like append is too much. Ghostty example was way too obvious (at least to me) and the other one feels like nitpicking to me. Making the good path easy to code is a nice principle but why remove append instead of just renaming it? Even that feels like too much of a defensive way of doing things because it is obvious that this may reallocate. If append did not exists in the way it did, people would just write their own. I know I would. But thats just me.

Tbh idk if removal would be what, maybe a rethink, but that capitalizing a file considers that is creating a struct with that same name it seems a little weird to me, specially for how it plays with comptime generics over that struct, as its (imo) very common and makes the capitalizatoon not appliable.

In a lesser degree, i don’t quite like the return struct { code + functions }; but I get why is nice about it.

I love that structs get “methods” if you pass *@This()/Self, but at the end of the day i don’t enjoy that the capitalization of a file says something about the code within it, or that you can declare an struct without writing struct anywhere in the file :slight_smile:

1 Like

I feel like “complexity” is the wrong word here, because as you said both approach are very similar.

But good library design guides you toward better code. Like “Huffmann encoding of good code”. The code you want to favorize should be easy to write and read.

That’s why ArrayListUnmanaged became ArrayList.

Similarly append should be the no allocation version and the allocating one should be appendAllocate

6 Likes

That is not that bad of an idea. But my point was that we shouldn’t replace single function with 2.
What I think would be nice is to rename current append to appenUnsafe and new append, to be the combination of ensureCapasity and appendAssumeCapasity, cause I think the easiest option should be the safest one.

2 Likes

Just so we’re all on the same page, current append is effectively a combination of ensureTotalCapacity and appendAssumeCapacity:

append is:

pub fn append(self: *Self, gpa: Allocator, item: T) Allocator.Error!void {
    const new_item_ptr = try self.addOne(gpa);
    new_item_ptr.* = item;
}

and addOne is:

pub fn addOne(self: *Self, gpa: Allocator) Allocator.Error!*T {
    // This can never overflow because `self.items` can never occupy the whole address space
    const newlen = self.items.len + 1;
    try self.ensureTotalCapacity(gpa, newlen);
    return self.addOneAssumeCapacity();
}

(for context, appendAssumeCapacity is self.addOneAssumeCapacity().* = item;)

2 Likes

If you actually had a valid point you would just make it using technical arguments rather than using such emotionally loaded language. You’re so frustrated by not being able to articulate anything substantial that you’re even taking a pot shot at people with Asperger’s for no reason. Grow up and learn to communicate like an adult.

28 Likes

I like the fact unused/unmutated variables are compile errors. If unintentional it is highly likely that it is a bug.
If intentional the intent can be made clear with _ = unused_thing. I also like that it is so easy to actually show this intent.
If it is considered to remove these errors I would like it to be optional.

I like the strictness.
Go to another language if you don’t like it.

To answer to the thread question: I have no internal wish to remove anything from what Zig offers right now.
append should not be removed :slight_smile:

1 Like

Don’t do this, we can’t tell people to switch to another language every time we disagree with them, let people have their opinions, otherwise we can’t have productive discussions about improvements.

There are many things I only partially agree with in this topic (and it doesn’t matter because I might be wrong or change my opinion), it is valuable to see what peoples concerns are and nobody here has the definite answer to how all things should be. It is fine for you to share your opinion, but do it without telling others you disagree with to leave.

28 Likes

Yeah true. Sorry I was a bit triggered late in the evening. Excuses to chung.

2 Likes

Thank you, I was gona read implementation in the morning after sleep, but this just shows that item can’t be left in invalid state if append fails. Good to know

The way I see the “append” problem is when you append things in loops, and the validity of the whole operation depends on the success of all append operations, without allocating upfront you’ll end up with invalid and unrecoverable data anyway if a single append fails. So it depends on whether you want to handle out of memory errors: if the plan is to just crash, you don’t need to care much about it, and it becomes (if anything) a performance concern (allocating once is better than N micro allocations).

For what it’s worth, I generally use a helper like this

pub inline fn aac(comptime T: type, list: *ArrayList(T), item: T) void {
    list.appendAssumeCapacity(item);
}

then call it like this

    al.aac(u8, &content, '\n');

which is short and has the benefit that you also see the type of what you’re appending.

No offense, but how long have you been writing Zig? I find that people that have used it for an extended amount of time just kind of forget that the compiler even enforces this, as you just tend to not make this mistake. And the times it does actually error, I am quite thankful as it finds an obvious bug/smell that has been introduced due to a refactor for example. Also don’t think that the compiler should make any assumptions about the authors setup, it sounds silly but why would the compiler assume the author has syntax highlighting or a linter? If it is something that can be caught at compile time, it should, IMO.

1 Like

In most other languages, unused variables result in a compiler warning and you can configure it in a way to treat this warning as an error. That’s what I would prefer. The error is in fact very annoying during debugging, breaking the “flow”.

Regarding append, I think it is crucial to keep it. Some people seem to think that each call causes allocation. That’s not true, pls read the code. The implementation uses a reasonable trade-off between performance and memory overhead.

2 Likes

In most other languages, unused variables result in a compiler warning and you can configure it in a way to treat this warning as an error.

I suppose I just fundamentally disagree with this solution, it is taking no stance on the matter, and just kicking the decision down to the user. I would prefer no warning at all over this :confused:

I just read the code:
append calls addOne(gpa) calls ensureTotalCapacity with newlen += 1 calls ensureTotalCapacityPrecise with an additional length of a cachline or one element, whichever is larger.
edit: Link: Zig Documentation

The ideal reallocation strategy would be to grow the array by a factor of the golden ratio. But for some systems memory is so limited that this would quickly run into OOM.
Still, I like append. And if possible I preallocate the capacity I need. But sometimes I don’t know how much i will need.