Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

For loop range is unusable with types other than usize #17039

Closed
TeamPuzel opened this issue Sep 1, 2023 · 7 comments
Closed

For loop range is unusable with types other than usize #17039

TeamPuzel opened this issue Sep 1, 2023 · 7 comments

Comments

@TeamPuzel
Copy link

TeamPuzel commented Sep 1, 2023

Zig Version

0.11.0

Steps to Reproduce and Observed Behavior

I have this function:

pub fn sprite(data: *const Sprite, x: u8, y: u8) void {
    std.debug.assert(x <= 127);
    std.debug.assert(y <= 127);
    
    for (data, 0..8) |column, sy| {
        for (column, 0..8) |color, sx| {
            if (x + sx > 127 or y + sy > 127) break;
            const sxc: u8 = @intCast(sx);
            const syc: u8 = @intCast(sy);
            if (color != transparency) pixel(sxc + x, syc + y, color);
        }
    }
}

where Sprite is [8][8]Color

(Ignore that the function has bugs, that's not really the point 🙂)

Doesn't matter if I use 0.. or 0..8 it still doesn't compile, as the range is seemingly inferred to be usize.

The only way to make it work is to use the two casts as shown in the function above to make the compiler happy.
This is annoying because this range is compile time known, so it should be clear it can fit in a u8.

Expected Behavior

I expected to be able to use u8 in loops.

@TeamPuzel TeamPuzel added the bug Observed behavior contradicts documented or intended behavior label Sep 1, 2023
@IntegratedQuantum
Copy link
Contributor

I would also love to have more control over the index type.
However Andrew mentioned in a related proposal that there would be problems with the compiler implementation.

@TeamPuzel
Copy link
Author

TeamPuzel commented Sep 1, 2023

Oh well. is the problem just inference? Or is it not even possible to have the choice to manually choose what type the range is?

I know that overdoing inference can be exponentially bad, I have written statements in Swift before that just couldn't compile :)
(And I assume the same is true for Rust as it is very similar)

The only workaround I can think of is emulating a C style loop with a while loop, which would honestly be awful.

Either way this feels a bit out of character for a language sold on the level of control it provides.

@TeamPuzel
Copy link
Author

TeamPuzel commented Sep 1, 2023

Is there at least no performance cost when using intCast?
I assume there must be :/

@rohlem
Copy link
Contributor

rohlem commented Sep 1, 2023

Is there at least no performance cost when using @intCast?

@intCast works by employing safety-checked illegal behavior.
(Note: The name "undefined behavior" here is remnant C terminology and inaccurate; the langref should IMO be updated in this regard - but see also #2301 .)
The short explanation is that the compiler is told what behavior you expect (the value should fit into the target type), and other behavior is illegal.

In safe build modes (Debug and ReleaseSafe) and blocks with explicit safety, the overhead is that if the assumption turns out wrong, @panic is triggered, which prints a stack trace and aborts the program.
In unsafe build modes (ReleaseFast and ReleaseSmall), the compiler is allowed to rely on the provided assumption (that illegal behavior cannot occur). Usually this is used to optimize the program, i.e. the compiler tries to always improve performance, and should never penalize you for stating such assumptions. (This hasn't really been formalized yet though.)

Technically you could find some performance cost for originally allocating the usize instead of just the u8 on the stack, but since for loops are language primitives and if there's no other dependency on the value before the cast, it should always be quite trivial for the compiler to see the equivalence in optimized build modes (all but Debug).

@nektro
Copy link
Contributor

nektro commented Sep 1, 2023

@IntegratedQuantum hmm that comment was made before @min and @max were adjusted to return a result based on the operands too so i wonder if that bodes well for this getting in after all

@Vexu Vexu changed the title For loop range is unusable with types smaller than usize For loop range is unusable with types other than usize Sep 13, 2023
@betonowy
Copy link

betonowy commented Mar 7, 2024

I did find one way to do something similar with comptime, it works for me:

    fn i32Range(comptime a: i32, comptime b: i32) [b - a]i32 {
        comptime {
            var range = std.mem.zeroes([b - a]i32);
            for (range[0..], 0..) |*v, i| v.* = a + @as(i32, i);
            return range;
        }
    }
    // ...
    // somewhere in code you do this
    inline for (comptime i32Range(-1, 2)) |i| {
        // I guess "i" is now comptime known and is of type "i32"
    }

Hope it helps anyone who stumbles in here searching similar issues like I did.

@andrewrk
Copy link
Member

working as designed; duplicate of #14704.

there is a chance you may get your wish with #3806.

@andrewrk andrewrk closed this as not planned Won't fix, can't repro, duplicate, stale Aug 16, 2024
@andrewrk andrewrk removed the bug Observed behavior contradicts documented or intended behavior label Aug 16, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants