const Iterator: type = switch (builtin.os.tag) {
        .macos, .ios, .freebsd, .netbsd, .dragonfly, .openbsd, .solaris => struct {
            dir: Dir,
            seek: i64,
            buf: [1024]u8, // TODO align(@alignOf(os.system.dirent)),
            index: usize,
            end_index: usize,
            first_iter: bool,

            const Self = @This();

            pub const Error = IteratorError;

            /// Memory such as file names referenced in this returned entry becomes invalid
            /// with subsequent calls to `next`, as well as when this `Dir` is deinitialized.
            pub fn next(self: *Self) Error!?Entry {
                switch (builtin.os.tag) {
                    .macos, .ios => return self.nextDarwin(),
                    .freebsd, .netbsd, .dragonfly, .openbsd => return self.nextBsd(),
                    .solaris => return self.nextSolaris(),
                    else => @compileError("unimplemented"),
                }
            }

            fn nextDarwin(self: *Self) !?Entry {
                start_over: while (true) {
                    if (self.index >= self.end_index) {
                        if (self.first_iter) {
                            std.os.lseek_SET(self.dir.fd, 0) catch unreachable; // EBADF here likely means that the Dir was not opened with iteration permissions
                            self.first_iter = false;
                        }
                        const rc = os.system.__getdirentries64(
                            self.dir.fd,
                            &self.buf,
                            self.buf.len,
                            &self.seek,
                        );
                        if (rc == 0) return null;
                        if (rc < 0) {
                            switch (os.errno(rc)) {
                                .BADF => unreachable, // Dir is invalid or was opened without iteration ability
                                .FAULT => unreachable,
                                .NOTDIR => unreachable,
                                .INVAL => unreachable,
                                else => |err| return os.unexpectedErrno(err),
                            }
                        }
                        self.index = 0;
                        self.end_index = @as(usize, @intCast(rc));
                    }
                    const darwin_entry = @as(*align(1) os.system.dirent, @ptrCast(&self.buf[self.index]));
                    const next_index = self.index + darwin_entry.reclen();
                    self.index = next_index;

                    const name = @as([*]u8, @ptrCast(&darwin_entry.d_name))[0..darwin_entry.d_namlen];

                    if (mem.eql(u8, name, ".") or mem.eql(u8, name, "..") or (darwin_entry.d_ino == 0)) {
                        continue :start_over;
                    }

                    const entry_kind: Entry.Kind = switch (darwin_entry.d_type) {
                        os.DT.BLK => .block_device,
                        os.DT.CHR => .character_device,
                        os.DT.DIR => .directory,
                        os.DT.FIFO => .named_pipe,
                        os.DT.LNK => .sym_link,
                        os.DT.REG => .file,
                        os.DT.SOCK => .unix_domain_socket,
                        os.DT.WHT => .whiteout,
                        else => .unknown,
                    };
                    return Entry{
                        .name = name,
                        .kind = entry_kind,
                    };
                }
            }

            fn nextSolaris(self: *Self) !?Entry {
                start_over: while (true) {
                    if (self.index >= self.end_index) {
                        if (self.first_iter) {
                            std.os.lseek_SET(self.dir.fd, 0) catch unreachable; // EBADF here likely means that the Dir was not opened with iteration permissions
                            self.first_iter = false;
                        }
                        const rc = os.system.getdents(self.dir.fd, &self.buf, self.buf.len);
                        switch (os.errno(rc)) {
                            .SUCCESS => {},
                            .BADF => unreachable, // Dir is invalid or was opened without iteration ability
                            .FAULT => unreachable,
                            .NOTDIR => unreachable,
                            .INVAL => unreachable,
                            else => |err| return os.unexpectedErrno(err),
                        }
                        if (rc == 0) return null;
                        self.index = 0;
                        self.end_index = @as(usize, @intCast(rc));
                    }
                    const entry = @as(*align(1) os.system.dirent, @ptrCast(&self.buf[self.index]));
                    const next_index = self.index + entry.reclen();
                    self.index = next_index;

                    const name = mem.sliceTo(@as([*:0]u8, @ptrCast(&entry.d_name)), 0);
                    if (mem.eql(u8, name, ".") or mem.eql(u8, name, ".."))
                        continue :start_over;

                    // Solaris dirent doesn't expose d_type, so we have to call stat to get it.
                    const stat_info = os.fstatat(
                        self.dir.fd,
                        name,
                        os.AT.SYMLINK_NOFOLLOW,
                    ) catch |err| switch (err) {
                        error.NameTooLong => unreachable,
                        error.SymLinkLoop => unreachable,
                        error.FileNotFound => unreachable, // lost the race
                        else => |e| return e,
                    };
                    const entry_kind: Entry.Kind = switch (stat_info.mode & os.S.IFMT) {
                        os.S.IFIFO => .named_pipe,
                        os.S.IFCHR => .character_device,
                        os.S.IFDIR => .directory,
                        os.S.IFBLK => .block_device,
                        os.S.IFREG => .file,
                        os.S.IFLNK => .sym_link,
                        os.S.IFSOCK => .unix_domain_socket,
                        os.S.IFDOOR => .door,
                        os.S.IFPORT => .event_port,
                        else => .unknown,
                    };
                    return Entry{
                        .name = name,
                        .kind = entry_kind,
                    };
                }
            }

            fn nextBsd(self: *Self) !?Entry {
                start_over: while (true) {
                    if (self.index >= self.end_index) {
                        if (self.first_iter) {
                            std.os.lseek_SET(self.dir.fd, 0) catch unreachable; // EBADF here likely means that the Dir was not opened with iteration permissions
                            self.first_iter = false;
                        }
                        const rc = if (builtin.os.tag == .netbsd)
                            os.system.__getdents30(self.dir.fd, &self.buf, self.buf.len)
                        else
                            os.system.getdents(self.dir.fd, &self.buf, self.buf.len);
                        switch (os.errno(rc)) {
                            .SUCCESS => {},
                            .BADF => unreachable, // Dir is invalid or was opened without iteration ability
                            .FAULT => unreachable,
                            .NOTDIR => unreachable,
                            .INVAL => unreachable,
                            // Introduced in freebsd 13.2: directory unlinked but still open.
                            // To be consistent, iteration ends if the directory being iterated is deleted during iteration.
                            .NOENT => return null,
                            else => |err| return os.unexpectedErrno(err),
                        }
                        if (rc == 0) return null;
                        self.index = 0;
                        self.end_index = @as(usize, @intCast(rc));
                    }
                    const bsd_entry = @as(*align(1) os.system.dirent, @ptrCast(&self.buf[self.index]));
                    const next_index = self.index + bsd_entry.reclen();
                    self.index = next_index;

                    const name = @as([*]u8, @ptrCast(&bsd_entry.d_name))[0..bsd_entry.d_namlen];

                    const skip_zero_fileno = switch (builtin.os.tag) {
                        // d_fileno=0 is used to mark invalid entries or deleted files.
                        .openbsd, .netbsd => true,
                        else => false,
                    };
                    if (mem.eql(u8, name, ".") or mem.eql(u8, name, "..") or
                        (skip_zero_fileno and bsd_entry.d_fileno == 0))
                    {
                        continue :start_over;
                    }

                    const entry_kind: Entry.Kind = switch (bsd_entry.d_type) {
                        os.DT.BLK => .block_device,
                        os.DT.CHR => .character_device,
                        os.DT.DIR => .directory,
                        os.DT.FIFO => .named_pipe,
                        os.DT.LNK => .sym_link,
                        os.DT.REG => .file,
                        os.DT.SOCK => .unix_domain_socket,
                        os.DT.WHT => .whiteout,
                        else => .unknown,
                    };
                    return Entry{
                        .name = name,
                        .kind = entry_kind,
                    };
                }
            }

            pub fn reset(self: *Self) void {
                self.index = 0;
                self.end_index = 0;
                self.first_iter = true;
            }
        },
        .haiku => struct {
            dir: Dir,
            buf: [1024]u8, // TODO align(@alignOf(os.dirent64)),
            index: usize,
            end_index: usize,
            first_iter: bool,

            const Self = @This();

            pub const Error = IteratorError;

            /// Memory such as file names referenced in this returned entry becomes invalid
            /// with subsequent calls to `next`, as well as when this `Dir` is deinitialized.
            pub fn next(self: *Self) Error!?Entry {
                start_over: while (true) {
                    // TODO: find a better max
                    const HAIKU_MAX_COUNT = 10000;
                    if (self.index >= self.end_index) {
                        if (self.first_iter) {
                            std.os.lseek_SET(self.dir.fd, 0) catch unreachable; // EBADF here likely means that the Dir was not opened with iteration permissions
                            self.first_iter = false;
                        }
                        const rc = os.system._kern_read_dir(
                            self.dir.fd,
                            &self.buf,
                            self.buf.len,
                            HAIKU_MAX_COUNT,
                        );
                        if (rc == 0) return null;
                        if (rc < 0) {
                            switch (os.errno(rc)) {
                                .BADF => unreachable, // Dir is invalid or was opened without iteration ability
                                .FAULT => unreachable,
                                .NOTDIR => unreachable,
                                .INVAL => unreachable,
                                else => |err| return os.unexpectedErrno(err),
                            }
                        }
                        self.index = 0;
                        self.end_index = @as(usize, @intCast(rc));
                    }
                    const haiku_entry = @as(*align(1) os.system.dirent, @ptrCast(&self.buf[self.index]));
                    const next_index = self.index + haiku_entry.reclen();
                    self.index = next_index;
                    const name = mem.sliceTo(@as([*:0]u8, @ptrCast(&haiku_entry.d_name)), 0);

                    if (mem.eql(u8, name, ".") or mem.eql(u8, name, "..") or (haiku_entry.d_ino == 0)) {
                        continue :start_over;
                    }

                    var stat_info: os.Stat = undefined;
                    const rc = os.system._kern_read_stat(
                        self.dir.fd,
                        &haiku_entry.d_name,
                        false,
                        &stat_info,
                        0,
                    );
                    if (rc != 0) {
                        switch (os.errno(rc)) {
                            .SUCCESS => {},
                            .BADF => unreachable, // Dir is invalid or was opened without iteration ability
                            .FAULT => unreachable,
                            .NOTDIR => unreachable,
                            .INVAL => unreachable,
                            else => |err| return os.unexpectedErrno(err),
                        }
                    }
                    const statmode = stat_info.mode & os.S.IFMT;

                    const entry_kind: Entry.Kind = switch (statmode) {
                        os.S.IFDIR => .directory,
                        os.S.IFBLK => .block_device,
                        os.S.IFCHR => .character_device,
                        os.S.IFLNK => .sym_link,
                        os.S.IFREG => .file,
                        os.S.IFIFO => .named_pipe,
                        else => .unknown,
                    };

                    return Entry{
                        .name = name,
                        .kind = entry_kind,
                    };
                }
            }

            pub fn reset(self: *Self) void {
                self.index = 0;
                self.end_index = 0;
                self.first_iter = true;
            }
        },
        .linux => struct {
            dir: Dir,
            // The if guard is solely there to prevent compile errors from missing `linux.dirent64`
            // definition when compiling for other OSes. It doesn't do anything when compiling for Linux.
            buf: [1024]u8 align(if (builtin.os.tag != .linux) 1 else @alignOf(linux.dirent64)),
            index: usize,
            end_index: usize,
            first_iter: bool,

            const Self = @This();
            const linux = os.linux;

            pub const Error = IteratorError;

            /// Memory such as file names referenced in this returned entry becomes invalid
            /// with subsequent calls to `next`, as well as when this `Dir` is deinitialized.
            pub fn next(self: *Self) Error!?Entry {
                return self.nextLinux() catch |err| switch (err) {
                    // To be consistent across platforms, iteration ends if the directory being iterated is deleted during iteration.
                    // This matches the behavior of non-Linux UNIX platforms.
                    error.DirNotFound => null,
                    else => |e| return e,
                };
            }

            pub const ErrorLinux = error{DirNotFound} || IteratorError;

            /// Implementation of `next` that can return `error.DirNotFound` if the directory being
            /// iterated was deleted during iteration (this error is Linux specific).
            pub fn nextLinux(self: *Self) ErrorLinux!?Entry {
                start_over: while (true) {
                    if (self.index >= self.end_index) {
                        if (self.first_iter) {
                            std.os.lseek_SET(self.dir.fd, 0) catch unreachable; // EBADF here likely means that the Dir was not opened with iteration permissions
                            self.first_iter = false;
                        }
                        const rc = linux.getdents64(self.dir.fd, &self.buf, self.buf.len);
                        switch (linux.getErrno(rc)) {
                            .SUCCESS => {},
                            .BADF => unreachable, // Dir is invalid or was opened without iteration ability
                            .FAULT => unreachable,
                            .NOTDIR => unreachable,
                            .NOENT => return error.DirNotFound, // The directory being iterated was deleted during iteration.
                            .INVAL => return error.Unexpected, // Linux may in some cases return EINVAL when reading /proc/$PID/net.
                            .ACCES => return error.AccessDenied, // Do not have permission to iterate this directory.
                            else => |err| return os.unexpectedErrno(err),
                        }
                        if (rc == 0) return null;
                        self.index = 0;
                        self.end_index = rc;
                    }
                    const linux_entry = @as(*align(1) linux.dirent64, @ptrCast(&self.buf[self.index]));
                    const next_index = self.index + linux_entry.reclen();
                    self.index = next_index;

                    const name = mem.sliceTo(@as([*:0]u8, @ptrCast(&linux_entry.d_name)), 0);

                    // skip . and .. entries
                    if (mem.eql(u8, name, ".") or mem.eql(u8, name, "..")) {
                        continue :start_over;
                    }

                    const entry_kind: Entry.Kind = switch (linux_entry.d_type) {
                        linux.DT.BLK => .block_device,
                        linux.DT.CHR => .character_device,
                        linux.DT.DIR => .directory,
                        linux.DT.FIFO => .named_pipe,
                        linux.DT.LNK => .sym_link,
                        linux.DT.REG => .file,
                        linux.DT.SOCK => .unix_domain_socket,
                        else => .unknown,
                    };
                    return Entry{
                        .name = name,
                        .kind = entry_kind,
                    };
                }
            }

            pub fn reset(self: *Self) void {
                self.index = 0;
                self.end_index = 0;
                self.first_iter = true;
            }
        },
        .windows => struct {
            dir: Dir,
            buf: [1024]u8 align(@alignOf(os.windows.FILE_BOTH_DIR_INFORMATION)),
            index: usize,
            end_index: usize,
            first_iter: bool,
            name_data: [MAX_NAME_BYTES]u8,

            const Self = @This();

            pub const Error = IteratorError;

            /// Memory such as file names referenced in this returned entry becomes invalid
            /// with subsequent calls to `next`, as well as when this `Dir` is deinitialized.
            pub fn next(self: *Self) Error!?Entry {
                while (true) {
                    const w = os.windows;
                    if (self.index >= self.end_index) {
                        var io: w.IO_STATUS_BLOCK = undefined;
                        const rc = w.ntdll.NtQueryDirectoryFile(
                            self.dir.fd,
                            null,
                            null,
                            null,
                            &io,
                            &self.buf,
                            self.buf.len,
                            .FileBothDirectoryInformation,
                            w.FALSE,
                            null,
                            if (self.first_iter) @as(w.BOOLEAN, w.TRUE) else @as(w.BOOLEAN, w.FALSE),
                        );
                        self.first_iter = false;
                        if (io.Information == 0) return null;
                        self.index = 0;
                        self.end_index = io.Information;
                        switch (rc) {
                            .SUCCESS => {},
                            .ACCESS_DENIED => return error.AccessDenied, // Double-check that the Dir was opened with iteration ability

                            else => return w.unexpectedStatus(rc),
                        }
                    }

                    const dir_info: *w.FILE_BOTH_DIR_INFORMATION = @ptrCast(@alignCast(&self.buf[self.index]));
                    if (dir_info.NextEntryOffset != 0) {
                        self.index += dir_info.NextEntryOffset;
                    } else {
                        self.index = self.buf.len;
                    }

                    const name_utf16le = @as([*]u16, @ptrCast(&dir_info.FileName))[0 .. dir_info.FileNameLength / 2];

                    if (mem.eql(u16, name_utf16le, &[_]u16{'.'}) or mem.eql(u16, name_utf16le, &[_]u16{ '.', '.' }))
                        continue;
                    // Trust that Windows gives us valid UTF-16LE
                    const name_utf8_len = std.unicode.utf16leToUtf8(self.name_data[0..], name_utf16le) catch unreachable;
                    const name_utf8 = self.name_data[0..name_utf8_len];
                    const kind: Entry.Kind = blk: {
                        const attrs = dir_info.FileAttributes;
                        if (attrs & w.FILE_ATTRIBUTE_DIRECTORY != 0) break :blk .directory;
                        if (attrs & w.FILE_ATTRIBUTE_REPARSE_POINT != 0) break :blk .sym_link;
                        break :blk .file;
                    };
                    return Entry{
                        .name = name_utf8,
                        .kind = kind,
                    };
                }
            }

            pub fn reset(self: *Self) void {
                self.index = 0;
                self.end_index = 0;
                self.first_iter = true;
            }
        },
        .wasi => struct {
            dir: Dir,
            buf: [1024]u8, // TODO align(@alignOf(os.wasi.dirent_t)),
            cookie: u64,
            index: usize,
            end_index: usize,

            const Self = @This();

            pub const Error = IteratorError;

            /// Memory such as file names referenced in this returned entry becomes invalid
            /// with subsequent calls to `next`, as well as when this `Dir` is deinitialized.
            pub fn next(self: *Self) Error!?Entry {
                return self.nextWasi() catch |err| switch (err) {
                    // To be consistent across platforms, iteration ends if the directory being iterated is deleted during iteration.
                    // This matches the behavior of non-Linux UNIX platforms.
                    error.DirNotFound => null,
                    else => |e| return e,
                };
            }

            pub const ErrorWasi = error{DirNotFound} || IteratorError;

            /// Implementation of `next` that can return platform-dependent errors depending on the host platform.
            /// When the host platform is Linux, `error.DirNotFound` can be returned if the directory being
            /// iterated was deleted during iteration.
            pub fn nextWasi(self: *Self) ErrorWasi!?Entry {
                // We intentinally use fd_readdir even when linked with libc,
                // since its implementation is exactly the same as below,
                // and we avoid the code complexity here.
                const w = os.wasi;
                start_over: while (true) {
                    // According to the WASI spec, the last entry might be truncated,
                    // so we need to check if the left buffer contains the whole dirent.
                    if (self.end_index - self.index < @sizeOf(w.dirent_t)) {
                        var bufused: usize = undefined;
                        switch (w.fd_readdir(self.dir.fd, &self.buf, self.buf.len, self.cookie, &bufused)) {
                            .SUCCESS => {},
                            .BADF => unreachable, // Dir is invalid or was opened without iteration ability
                            .FAULT => unreachable,
                            .NOTDIR => unreachable,
                            .INVAL => unreachable,
                            .NOENT => return error.DirNotFound, // The directory being iterated was deleted during iteration.
                            .NOTCAPABLE => return error.AccessDenied,
                            else => |err| return os.unexpectedErrno(err),
                        }
                        if (bufused == 0) return null;
                        self.index = 0;
                        self.end_index = bufused;
                    }
                    const entry = @as(*align(1) w.dirent_t, @ptrCast(&self.buf[self.index]));
                    const entry_size = @sizeOf(w.dirent_t);
                    const name_index = self.index + entry_size;
                    if (name_index + entry.d_namlen > self.end_index) {
                        // This case, the name is truncated, so we need to call readdir to store the entire name.
                        self.end_index = self.index; // Force fd_readdir in the next loop.
                        continue :start_over;
                    }
                    const name = self.buf[name_index .. name_index + entry.d_namlen];

                    const next_index = name_index + entry.d_namlen;
                    self.index = next_index;
                    self.cookie = entry.d_next;

                    // skip . and .. entries
                    if (mem.eql(u8, name, ".") or mem.eql(u8, name, "..")) {
                        continue :start_over;
                    }

                    const entry_kind: Entry.Kind = switch (entry.d_type) {
                        .BLOCK_DEVICE => .block_device,
                        .CHARACTER_DEVICE => .character_device,
                        .DIRECTORY => .directory,
                        .SYMBOLIC_LINK => .sym_link,
                        .REGULAR_FILE => .file,
                        .SOCKET_STREAM, .SOCKET_DGRAM => .unix_domain_socket,
                        else => .unknown,
                    };
                    return Entry{
                        .name = name,
                        .kind = entry_kind,
                    };
                }
            }

            pub fn reset(self: *Self) void {
                self.index = 0;
                self.end_index = 0;
                self.cookie = os.wasi.DIRCOOKIE_START;
            }
        },
        else => @compileError("unimplemented"),
    };