Why Zig doesn't check for (semantic) errors in unused references?

Consider the following:

var num: u8 = "";
pub fn main() !void {}

This code compiles without error. However, if I add the following:

var num: u8 = "";
pub fn main() !void {
    num = 1;
}

I get the expected:

src/test.zig:68:15: error: expected type 'u8', found '*const [0:0]u8'
var num: u8 = "";

I find it very inconvenient at times. I write something for future purposes, assuming it compiles, and then once you actually need it, you discover you did all kind of mistakes. Why is that?

1 Like

I’m not totally sure, but it looks like you are coercing the u8 into a slice type, since you are using double quotes which is the string literal syntax. So what I’m thinking is, is that calling var num: u8 = "" is not really an error. However when you go to reassign that variable back into an integer by calling num = 1, then really you are misusing the previous assignment. I would use = undefined when initializing an empty variable, if you want to save it for later.

So basically my opinion is that the first line of your code is not really a semantic error. Though I’m not really sure why you would want to do type coercion here. So you might argue that trying to do type coercion when initializing an empty variable should generate a warning.

Or is it that the top level value doesn’t get typechecked if it’s not (transitively) used from main?

2 Likes

I think so. This sound like it’s caused by zig’s lazy compile-time evaluation.

1 Like

Yep, same thing here:

fn func(a: u32) void {
    return a + 2;
}

pub fn main() void {}

Lesson: if you need smth – use it, don’t be smart about it. That’s the Zig way.

2 Likes

I think it’s safe to say this is de-emphasizing the validation policy outside of the main block. So my attitude would be to allows these kinds of unused declarations, almost like they are notes or comments. As long I was aware that when I decide to go and use them the first time that I will need to double-check everything first. This would be useful also because since they are not being used, then it’s reasonable to say you might go back and alter them before you need to use them anyway. So it wouldn’t do a lot of good to have typechecking here and it could even become a hindrance. Though I’m still curious if it would be practical to optionally output a warning here when passing specific compilation flags.

I would only add that it might have been better if there was already a clear explanation of what’s going on here. Instead of the OP having to learn their lesson from the compiler. So this might be a good topic for a getting started guide or the like.

I also don’t mind this behavior. FWIW, it’s similar to the “unused variable” error except that top-level declarations don’t seem to get the same treatment. I’m sure this is a contentious topic too, but I prefer not to get this error from top-level declarations, as this makes refactoring easier (f.ex., unused imports are not flagged as errors).

2 Likes

Keeping in line with the theme of the Explain category, I would wager that the primary reason for this behavior is performance (a.k.a. gotta go fast). There is a stated goal of the Zig compiler being as fast as possible, given that slow compile times have been the bane of languages like C++ and Rust. So if not evaluating anything that isn’t used or necessary (the branch body of a comptime if that evaluates to false is another example) makes you compile faster, that’s the route Zig will take. I’m not an authority on this but this is the explanation that pops up in my head when I first observed this behavior.

5 Likes

Not exactly a getting started guide, but the Language Reference has several examples of code not being evaluated at all given certain contexts. If you search the page for @compileError you’ll see most of them.

3 Likes

But now that Zig rejects var if there is no assignment later, the issue disappeared, no?

I think you are referring to Zig rejecting an unmutated var, not necessarily unused. and also it does appear that this is only checked in the main block as well

Edit: to be clear, Zig will not check for unused or unmutated outside of the main block, but will check for both otherwise

1 Like

I wish there was a “mark as the solution” button. Thanks to @tensorush and @mscott9437 as well. Your best guesses answer my question well.

2 Likes

This is the answer. Also, lazy analysis in general is important not just for faster/smaller compilations, but for certain semantics (conditional compilation).

@timfayz Note that, in the future, your first example would generate an error for an unrelated reason: being an unused non-pub global. compile errors for unused things · Issue #335 · ziglang/zig · GitHub

8 Likes

This is great. But it would be greater if an option can be provided to enable full analysis.

There is a proposal in "multibuilds" - a plan to harmonize conditional compilation with compile errors, documentation, and IDEs · Issue #3028 · ziglang/zig · GitHub

3 Likes