Skip to content

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

Closed as not planned
Closed as not planned

Description

@TeamPuzel

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.

Activity

  1. added
    bugObserved behavior contradicts documented or intended behavior
    on Sep 1, 2023
  2. IntegratedQuantum commented on Sep 1, 2023

    @IntegratedQuantum
    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.

  3. TeamPuzel commented on Sep 1, 2023

    @TeamPuzel
    Author

    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.

  4. TeamPuzel commented on Sep 1, 2023

    @TeamPuzel
    Author

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

  5. rohlem commented on Sep 1, 2023

    @rohlem
    Contributor

    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).

  6. nektro commented on Sep 1, 2023

    @nektro
    Contributor

    @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

  7. changed the title [-]For loop range is unusable with types smaller than usize[/-] [+]For loop range is unusable with types other than usize[/+] on Sep 13, 2023
  8. betonowy commented on Mar 7, 2024

    @betonowy

    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.

  9. andrewrk commented on Aug 16, 2024

    @andrewrk
    Member

    working as designed; duplicate of #14704.

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

  10. removed
    bugObserved behavior contradicts documented or intended behavior
    on Aug 16, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions