Not that this lint actually achieves that, or even prevents real errors. Go has the same, and it's so simplistic as to only be annoying. For instance not sure whether this fails in Zig but Go will allow this:
v1, err := Foo()
if err != nil {
return nil, err
}
v2, err := Bar(v1)
return v2, nil
Error of second call is never checked, but go has no issue with that, because it only tracks definitions per use. v1, err := Foo()
v2, err := Bar(v1)
if err != nil {
return nil, err
}
return v2, nil
also works fine, despite probably sending complete nonsense to Bar, for the same reason.But then it's an absolute pain in the ass every time you're fucking around and stop using a debug import or whatever.
export fn x() i32 {
var a: i32 = 1; // okay
a = 2;
return a;
}
This does not: export fn x() i32 {
var a: i32 = 1; // unused variable
var a2: i32 = 2;
return a2;
}
> Not that this lint actually achieves that, or even prevents real errors.I have to agree. If there's one thing I've learned over the years, it's that you can't prevent bad code by enforcing lint errors.
I completely disagree, I regularly catch mistakes/bugs in my own code due to enforced linting errors (both locally and in CI), and just in the past few months I can recall numerous instances where CI linting caught bugs in teammates' code too.
There are both false positives and false negatives with linting, but that doesn't make it useless at all.
And that's why this issue is kind of hard. It's why I end up block scoping a lot of error code in Go, which is ugly but generally safer.
It is.
> That `a` is reassigned doesn't make it unused. It is, after all, a variable.
But that's exactly why it's bad, "unused variable" is not a super useful signal in the first place, and if you assert that everything must be used then you need to protect against "unused stores" instead.
SSA tells us there is essentially no difference between assigning and reassigning.
A lot of simple cases can be handled with linting rules, BUT that, IMO requires a few additional things. Namely, being expression based, rather than statement based, and enforcing something like a hindley milner type system.
The missing error checks are annoying, but if you have appropriate editor config it is hard to miss them: https://cdn.billmill.org/static/newsyctmp/warning.png
Basically writing go without `staticcheck`[1] is not recommended. If you do have it set up, it's pretty easy to avoid simple errors like that. I do wish the compiler checked it for you.
Don’t get me wrong I like a good unused code warning.
What frustrates me is that Go’s is dumb / unreliable, and it will stop you from working entirely until you’ve complied with this whim, which has a fraction of a percent chance of identifying a real bug.
> Basically writing go without `staticcheck`[1] is not recommended.
So why have these things as mandatory compiler errors?
I guess they could have levels and allow them in debug mode or with special flag or something?
Why do they care?
> I guess they could have levels and allow them in debug mode or with special flag or something?
Well yes, that's how normal compilers do things.
Of course if you’re the only developer working on your own codebase, you’d wonder why they care but you’re also not the main group they were targeting.
So someone’s anal retentive was made into a langage mandate rather than anything actually useful?
> Given enough time and developers, things like unused variables will start to seep in and make the codebase dirtier.
Why are dead variables any dirtier than dead stores? Or unchecked errors?
If you mean stores to fresh local variables, I assume that should also count in a proper/ideal implementation of this.
If the idea is for code to be literally WYSISWYG, then your compiler will no longer eliminate variables or code for you, so you have to do it.
In principle, a dead variable in final code is also almost certainly a mistake: either by not including the variable in the computation or for declaring the variable at all. Maybe a little annoying while you're figuring something out, but makes sense in the context of polished code.
I mean stores for which there is no load.
> I assume that should also count in a proper/ideal implementation of this.
It certainly does in a proper implementation, but not in zig. Or go.
> If the idea is for code to be literally WYSISWYG, then your compiler will no longer eliminate variables or code for you, so you have to do it.
That sounds stupid.
> a dead variable in final code is also almost certainly a mistake
See “final code” is the issue at hand. The problem with go is that it specifically does not do that. It has a half-assed implementation which it forces upon you all the time.
> Maybe a little annoying while you're figuring something out
It’s a bloody nuisance when you new figuring things out or in the middle or refactoring something.
> but makes sense in the context of polished code.
There’s a ton more which makes sense “in the context of polished code” which go can’t be arsed to do.
Checking that errors are not implicitly ignored for starters.
Stores for which there are no load can still be observable in concurrent scenarios. That's why I specified stores to locals with no load because those are not observable.
> That sounds stupid.
Sounds totally reasonable to me in scenarios where you need the output of the compiler to be very predictable.
But most everyone else likes this, so it stays in.
There are no real right answers here. Adding a switch for it just brings its own annoyance (every compiler switch is a bug). You just wind up settling for a local optimum.
I totally get where this is coming from, but on the other hand it seems like Rust and Zig both get a lot of value from having the compiler understand the difference between debug and release modes, and it seems like modern C++ suffers somewhat from not having any built-in way to do something similar. The optimal number of compiler switches might not be zero.
It's not useless to have them but they can be utterly infuriating for exactly the reason you mention since most projects have warnings-are-errors turned on.
The way I shut it up in a class is usually by branching on the this pointer. Sneaky.
We simply have to have a very high bar for new features.
> hoping that you forget to remove the assert
I routinely add a lot of instrumentation when developing code. I use `git diff` to remove it before committing.
The compiler builds itself fast enough that this is not a problem. I have one window on the source, the other on the build.
What's damning is how much Stockholm Syndrome there is around this feature, with people saying it's no big deal and it helps catch bugs. It's more annoying than helpful, and it catches a very small amount of corner cases, while completely killing productivity.
And you know what's the reasoning behind this? "Zig doesn't have warnings." As if it's a massive undertaking to add warnings to a compiler. What a sorry excuse.
I have ranted about this before, so I already feel my blood pressure going up.
Setting aside the unused variables issue for a moment, you might want to take a moment to ponder the fact that not having warning messages is an explicit design choice, not a missing feature.
> Well if one never stops to think about its potential merits
Listing some of those merits may be more productive than insisting people aren't thinking about it hard enough
1. WYSIWIG compiler: how many people have been complaining lately about C compilers eliminating code on them due to undefined behaviour? If you eliminate certain optimizations so your complier doesn't do this, like dead code/variable elimination, then what the code looks like is exactly what executes. This means some important optimizations may have to be done by hand. Eliminating dead variables could be one of them.
2. Arguably dead variables are a mistake. Either they're a (major) mistake because you neglected to include a value in your computation, or a (minor) mistake because you declared a superfluous variable that's never used.
3. If you want your code to be clear and as self-documenting as possible, unused variables are an impediment. I can't speak to the accuracy of Zig's analysis, but I can see how it would be annoying while working out an algorithm.
Is it available somewhere? I can't think anything why that compiler patch[0] can't be upstream (of course not default, but under a flag/different build mode/config option).
[0]: https://github.com/ziglang/zig/issues/335#issuecomment-10138...
Says who? Package repo maintainers? -Werror is actually quite divisive.
I see this comment a lot in response to feedback: "Don't like it? Don't use it!" Every single time, it reads to me as "stop complaining!" For as many times as I've seen the comment, I haven't been able to read it any other way.
Am I being uncharitable or is it really just a clear-cut attempt to silence debate?
On the other hand in case cloud services like those provided Google/FB/Twitter/Cloudflare etc it is much more difficult to just go with "Don't use if you don't like" because not only there are not much alternative but also because sometimes Cloud service providers act in unison. So if one is pushed out from a service they are likely to be pushed from all other services too.
All of this says to me that Zig is taking the wrong approach. Making highly controversial, opinionated features the standard with no way to opt out just leads to these endless debates. Languages (or any tools, for that matter) that stay out of the debate and just give everyone a flag tend to have much more welcoming communities.
I’m not usually a “just don’t use it” kind of guy, but in this case it’s valid, imo.
Another situation where I think the compiler is too eager with errors: unreachable code. A bare `@panic("")` won't compile, but you can "turn off" the error by doing `if (true) @panic("")`.
Could it be that this problem is due to Zig's stance on "no warnings"? It's admirable to try and partition things into strict "correct" and "incorrect" categories. But with only static information it seems like there's always things that seem a little grey.
I love Zig. It's built on such a better foundation than any of its competitors, old or new. I've never been happier or more productive doing systems programming.
But the "no warnings" philosophy alienates developers and seriously hurts adoption. It's almost tragic that a language so impressive is going to die on this hill.
I originally looked forward to splitting my projects between Zig and Rust depending on the project parameters, but because of this one seemingly small issue I will never use Zig.
That's ok, I respect them having the right to do what they want with their language but it is a little sad because I liked pretty much everything else about Zig.
I had to work in python for a long time where code tends to be incredibly relaxed. When I switched to strict mypy typechecking at every build I became much much more productive and found and fixed dozens of bugs way earlier in development. I now am worried about all warnings and always strive to fix them.
I am also making an analogy to "not being allowed to smoke indoors" when I see these complaints. People hated those laws when they were first proposed but everyone's cool with having to do a tiny bit work for health and safety.
We'll see how communities play out, but I'm all for being rigorous.
I'd prefer `eslint-expect-error-next-line` instead to warm me when the comment is actually useless but it's better than what Zig seem to do.
I do wish Rust had something like this too
You can put it at the top of your file with a #! too, if you want it to apply to the whole file.
pub fn abc() -> usize {
#[allow(deprecated)]
"abc".len()
}
I want it to complain about the "allow", because there was never any deprecated warning emitted in the first place. Maybe it would be called #[expect(deprecated)]I refactor code all the time and find these stray "allow"s that aren't doing anything anymore
Both this and the reply saying you should open an RFC worry me that it was tongue-in-cheek.
Or maybe it's the obvious name choice, heh, because it literally exists with that very syntax: https://play.rust-lang.org/?version=nightly&mode=debug&editi...
Not stable yet, as you can see, but all the RFC and implementation work has been done, and a stabilization report was even put forward back in July: https://github.com/rust-lang/rust/issues/54503#issuecomment-...
The strict compiler check would have had exactly the opposite effect from the intended one.
Kelly is designing Zig to do nothing surprising or change things underneath you. Even C does things that are surprising.
If you tell the compiler "Hey, I know this is unused but I am going to write it anyway" then the compiler _can_ make decisions such as eliding the variable.
I think we do all agree that it is sensible that a variable in code that is not used should be a compiler warning (if it is an error or not is clearly debatable).
True, it's actively harmful.
> There is a big difference between an unused variable and explicitly defining an unused variable.
Which is not helpful when you're only doing it to hide a compiler error foisted upon you.
> Kelly is designing Zig to do nothing surprising or change things underneath you.
Refusing to compile my code because I've an unused variable is certainly surprising.
> If you tell the compiler "Hey, I know this is unused but I am going to write it anyway" then the compiler _can_ make decisions such as eliding the variable.
That is the opposite of making sense. If you are actively forcing the use of a variable then the compiler removing it anyway is exactly surprising.
> I think we do all agree that it is sensible that a variable in code that is not used should be a compiler warning (if it is an error or not is clearly debatable).
1. that it's an error is the entire problem here
2. and as far as I'm concerned it's a ridiculously weak warning, if your standard is that no action should be useless you need an unused store error, which subsume unused variables
I disagree, you are actively telling the compiler that this is dead code.
No, you would be doing that by removing the code. You are telling the compiler that this is code you want and to shut the fuck up.
Using `_ = var` to prevent the error is just as bad as handling Java exceptions with empty catch blocks.
It really irks me when there isn't a way to have granular control over silencing warnings.
you get used to it after some time - much better than for example rust, where you have to figure out all the lifetimes, muts and generic traits before.
They aren't comparable. Everything you mentioned in Rust is necessary for typechecking. Unused variable lints aren't necessary for anything.
I am saying that prototyping (for me) is much easier in Zig. Some people like to define all the types (and traits) first, if it works for them then it's ok. I like to get feedback on something working as soon as possible because I'm likely a bit wrong and I will need to refine or rethink the whole thing again, rust is putting obstacles in my way, Zig is not.
This makes a normal workflow with `watchexec zig test` basically impossible, since before I can even run the tests I have to spend time hunting down which variables are used/unused at the moment and (un)commenting them. And it seems like they're planning to double down on this by making you adjust even more trivial things like public/private and var/const before it will even compile your code. https://github.com/ziglang/zig/issues/335 https://github.com/ziglang/zig/issues/224
Maybe I'm a bad developer, but my code is never perfect the first time around. I always spend time experimenting and refining my designs. I want to find a design that works, and only then spend time polishing and prettifying before I git push.
I do understand the reasoning (they don't want people committing poor quality code), but this implementation just seems completely backwards to me. It breaks the natural order. It's like saying we won't let you ctrl+s until your tests pass to make sure you don't commit broken code. Stop nannying me and let me get my work done!