What Should We _Remove_ from Zig?

Basically, for larger arrays, it uses ~ 1.5 x the requested capacity. So, when you request space for 2000 items, it reserves space for 3000, so the next 1000 append calls do not need to allocate/resize.

Could you link to the code, because following the chain in the stdlib that i linked i did not see any size dependend reallocate.

Sorry, I’m on the phone. Look at growCapacity.

No worries. You can look it up when you are back at a proper computer. I also just left and have only my phone with me.

I can’t find the reference, but I think there was a development plan to output code more aggressively in the face of compilation error. At the time, I tought of it as transforming many errors into warnings, and even skipping function with compilation error (e.g. replace it with a panic). I didn’t pay too much attention at the time as it was not in the next release, but I thought of it as an acknowledgment that stopping at the first compilation error was not the best.

I wouldn’t call that something to remove, it’s more a feature request for non-fatal error, with a touch of more resilient parser that would be better at skipping broken functions/types.

The chain is append → addOne → ensureTotalCapacity → growCapacity. The definition of growCapacity is:

pub fn growCapacity(minimum: usize) usize {
    if (@sizeOf(T) == 0) return math.maxInt(usize);
    const init_capacity: comptime_int = @max(1, std.atomic.cache_line / @sizeOf(T));
    return minimum +| (minimum / 2 + init_capacity);
}

So basically minimum + minimum / 2 + cache_line = minimum * 1.5 + cache_line

3 Likes

While the reserver first concept is something to be preferred, it’s not necessarily something you can always properly do.
I at least don’t want to constantly write when the final element number is unknown to me beforehand:

try list.ensureUnusedCapacity(1);
list.appendAssumeCapacity(e);
1 Like

I think it would be nice to remove the error for “uselessly discarding” a value:

Here’s a slightly contrived example that I’ve modified from Zig Comptime ORM

pub fn update(bundle: *Bundle, value_new: Value) void {
    const id = value_new.id;
    assert(@intFromEnum(id) != 0);
    const value_old = bundle.get(value_new.id).?;
    assert(value_old.id == id);

    bundle.objects.remove(value_old);
    _ = value_old; // Don't mix up with value_new
    bundle.objects.insert(value_new);
}

This could also be useful for things like conversions:

const count = 10;
const size = count*@sizeOf(T);
_ = count; // Everything is now in bytes

I’ve had nasty bugs due to mixups like this pop up a couple of times (especially programming in C++ where editor tooling is really bad). Less when initially writing something, more when editing existing code, where it’s easier to miss small changes to the names of variables.

Explicitly discarding a value expresses my intent to never use it again, and if I change that decision later or accidentally mix things up, I’d have some friction that helps me think it through.

8 Likes

Hmm, hard to say, actually.
But probably inferred error sets on pub functions.
I think it creates sloppy interfaces for 3rd parties.

4 Likes

_ = doesn’t really “discard” stuff its more like touching it for lack of better words. I am wrong. (I don’t think I am wrong.) I would write your examples like:

pub fn update(bundle: *Bundle, value_new: Value) void {
    const id = value_new.id;
    assert(@intFromEnum(id) != 0);
    {
        const value_old = bundle.get(value_new.id).?;
        assert(value_old.id == id);
        bundle.objects.remove(value_old);
    }
    bundle.objects.insert(value_new);
}
const size = b: {
    const count = 10;
    break :b count * @sizeOf(T);
};
1 Like

The problem with the current approach is that during the WIP stage, we use _ = unused to temporarily suppress unused var error.
But this silencer is risky, if it was forget to be removed, unused variables will never trigger warnings in the final stage of development.
On the other hand, compiler warnings are always noticeable, so developers can tolerate them in the WIP stage, but not at all in the finished product stage.

3 Likes

That doesn’t work with function parameters

True. You have defeated me in the marketplace of ideas

Did some digging, the current behavior is the result of this proposal

1 Like

Sorry i take everything back. I have missread that definition. That makes append much better than i expected. Thank you for pointing it out

1 Like

One of the (many) problems with compiler warnings is that they don’t interact well with incremental compilation, which zig is trying to use to its fullest.

At least with _ = unused you can just grep your codebase for it, or you can add automated checks to CI/CD pipelines or pre-commit hooks. Since it’s just syntax, the automated checks don’t even need a working compiler!

2 Likes

Inferred error sets were a breath of fresh air when I first tried Zig, having just come from an awful experience trying to learn Rust where the error handling broke me long before the borrow checker could have. (This was several years ago, maybe it’s gotten better since then). I know there’s monsters here that I haven’t fully dealt with yet (and I’m still quietly holding out hope for error payloads one day), but I think it would be very sad if they were taken out.

I do have an issue with try though, and that’s that it’s a prefix operator, where all the other ‘unexpected value’ operators are postfix. It leads to some weird cases of having to add parentheses simply because I want to try part of the expression. I’d much prefer a .!.

3 Likes

One somewhat surprising fact is that explicit discards in the final code are very likely harmless; their presence doesn’t necessarily indicate a code smell. In certain implementations of interfaces, parameters that the interface requires but aren’t actually used are a benign case of explicit discards.
A tiny number of bad discard cases used to suppress errors during the WIP phase because of no warning mechanism can get mixed in with a large number of benign cases, making them hard to analyze.

Isn’t it kinda strange to have _ = unused but you can have dead functions all over . I know the compiler just omitted them . But in the spirit of clean as you go wouldn’t it make since to do the same with functions like fn blah() _ {}

It sort of would, I guess, but if the compiler subjected all declarations to semantic analysis, Zig’s ability to do conditional compilation would be significantly hampered. Particularly, requiring this error would be fairly problematic, since some functions are reached or not based on comptime data (like the target, or the optimization mode)

2 Likes