You couldn’t make it a compiler warning? To silent the warnings you use the _ return? It would be a handy built in way ti see how much dead air your have or maybe a compiler flag?
I don’t want a bunch of compiler warnings whenever platform-specific code is omitted. It’s distracting and would train people to ignore the warnings. Status quo on this is perfectly fine IMO.
I’m with Zig status quo: I would like to never see any warnings emitted by the compiler.
Just FYI, here’s the PR where the current growth algorithm was implemented, which has some charts: std: remove loop from growCapacity by andrewrk · Pull Request #25302 · ziglang/zig · GitHub
Relevant issue is Proposal to catch a class of bugs by adjusting the reporting of discarded locals and parameters · Issue #16926 · ziglang/zig · GitHub
One thing I have a question of is the automatic trailing NUL in string literals. Would it make sense to make that opt-in, specifically for APIs that require sentinel-terminated strings?
I was reading The Worst Mistake of Computer Science, particularly the “Strings” subsection discussing NUL-terminated strings.
I understand Zig’s design is different: literals retain their length, and ordinary slices don’t rely on scanning for a terminator. So I’m not suggesting Zig simply repeats C’s mistake.
My question is about the default: why should an ordinary string literal guarantee a trailing zero even when it is used entirely within length-aware Zig code? Could that requirement be expressed explicitly where needed, while ordinary literals remain byte arrays without a sentinel?
I can see the convenience for C interoperability. I’m curious what other trade-offs favor the current design, and what an opt-in approach would make worse.
I think the advantage C interoperability clearly dominates the overhead of 1 byte per string literal. A typical program will not have thousands of string literals.
Another benefit is that when the strings get packed into the binary, they end up with a null character separating each one. That means you can read them out with the strings utility, for example. It’s not a big benefit, but sometimes comes in handy when debugging.
I understand the convenience, but I’d personally prefer C-specific representation requirements to be handled explicitly in an interoperability layer. Given all the engineering work that goes into making Zig efficient and powerful, I think it’s reasonable to question even a small default cost when it contributes nothing to code that exclusively uses strings with explicit lengths.
The “Strings” section of this article, which also links to “The Most Expensive One-Byte Mistake”, is what prompted my concern. I recognize that retaining the length allows Zig to avoid the problems described when using slices, so the trailing byte alone doesn’t mean Zig inherits those bugs. For code that only uses explicit lengths, the terminator appears unnecessary. I understand that removing it would not solve embedded-NUL issues at C API boundaries; those would still require validation or length-aware APIs. My question is specifically whether providing the terminator by default is preferable to making it explicit where needed.
The question here is could ordinary literals omit the terminator and offer an explicit way to generate C-compatible literals at compile time, alongside adapters for data obtained at runtime? I’d like interoperability to preserve Zig’s freedom to choose its own representations. What would that approach cost compared with the current design?
I can imagine some future version of Zig that knew which string literals didn’t need NUL terminators in some similar way to knowing which functions are unused. It sounds technically appropriate but complicated, and I can’t imagine the value gained in bytes ever being something I would ever notice. Like, maybe there’s a sound implementation plan, but it sounds really low priority. And C integration is pretty important, or at least I think so.
In the meantime, does Zig do what you want if you explicitly declare the type?
E.g. const want_no_nul: []const u8 = “test”;
Having the compiler omit unnecessary terminators seems like a reasonable alternative. I understand why you would consider this low priority, given the potential savings versus the implementation complexity.
Regarding your example, the documentation indicates that "test" has the type *const [4:0]u8. Converting it to []const u8 creates a slice over the same data; this removes the type’s sentinel-value guarantee but does not remove the terminator from the underlying array. I haven’t checked whether the optimizer already removes it in certain cases.
I also acknowledge that my initial comparison to C string issues was too broad: Zig slices already provide explicit lengths, so removing the final byte wouldn’t reintroduce those problems. My preference is simply to make the extra storage optional based on need, though automatic omission could also address that concern, leaving it to the programmer to write it explicitly only when necessary, 0 overhead.
I mean, the literal has a NUL, but I read it as the comptime value not having a NUL. I was reading it as the literal being an input to a comptime expression which includes a type cast. But I’ve no idea if I’m right or not. There’s no visible effect on the program except storing an extra byte which will never be read, so I can imagine it working either way - I’ve never looked into it, I don’t have that problem, it’s just what I’d try first to see if I can get my extra byte back.
I’d also like to see fallibleFunction() catch { ... } removed. If you want to ignore the error, just discard it: fallibleFunction() catch |_| { ... }.
Another thing (in the stdlib) that seems good to remove is MultiArrayList.Slice. It’s some sort of ugly sibling of MultiArrayList proper: have a Slice and want to clone it? That’ll be (try slice.toMultiArrayList().clone(gpa)).slice(); of course!
Other things (that I think the core team already knows) are //! comments and allowing ambiguous operator precedence.
Something that’s not in the language but is an accepted proposal is the confabulation of “math vectors” and “simd vectors” and suggesting they should be interchangeable (https://codeberg.org/ziglang/zig/issues/35376). Basically: “wide simd good, narrow simd questionable”. Though I think the comments there already mention this as well.
Oh and I’d like the auto-usize of for loop captures to be removed; if n is of type u8 I’d like for (0..n) |k| { ... } to assign k the type u8 instead of usize (or is that a feature addition? :^)
I can’t see what you wrote that was flagged here, but I’m responding anyway: I didn’t mean to be unkind. I do know there are platforms where every byte matters, what I meant to do was explain why I thought there might be a way to do it in the existing language and ask if you or somebody else had tried it.
Dont worry, I was flagged for using AI for translation, I’ll try my best to communicate in English.
So basically, if I understand it correctly, this slice points to a string literal whose storage includes an extra byte for a terminator:
const text: []const u8 = "test";
But you can create without it this way:
const bytes: [4]u8 = "test".*; // Dereferences the literals pointer to end up with a 4-byte array without the terminator
//New slice pointer looking at those same 4 bytes but without terminator
const text: []const u8 = &bytes;
So both slices have the same type and lenght but the diference is the storage behind the scenes.
I understand the advantage for C interop but couldnt be the programmer the one that request explicitly a terminated string when an API needs one? Why is it the default?
Compared to the other proposals I looked at, this makes the most sense to me. Big “+1” from me for these two points (2 and 3).
Yes - when I’m wrapping a function and want to ensure a certain error that it could return once it got changed gets handled by a switch statement in the outer function - even if the inner function currently does not yet return it (e.g. because that’s part of missing functionality that needs to be implemented later).
EDIT - What I wrote above is contrasting !T (inferred errors) with anyerror!T. But I could also just explicitly state the error set (error{ A, B, C }!T) without actually having to return any of these errors. When I wrote the above I forgot that this is valid code:
fn f() error{ ICouldPotentiallyBeReturnedAtALaterPointButNowImNot }!void {}
I’m not sure what your point is, a slice is just a data structure for pointing to a portion of an array, it doesn’t care where the array is or how it is allocated.
the point is that the array it slices has a null terminator behind the scenes, ie, not explicitly demanded.