Zig 0.17.0-dev (Split by item)

This is an example of documentation generated by ZigDoc, an alternative to Zig's built-in Auto Doc feature. See also examples in other modes/formats. The project being documented here (as the example) is the Zig library itself.

Map

Each key and each value are allocated independently and owned by this data structure.

Environ.Map
pub const Map = struct

File

lib/std/process/Environ.zig:100

Code

pub const Map = struct {
    array_hash_map: ArrayHashMap,
    allocator: Allocator,

    const ArrayHashMap = std.array_hash_map.Custom([]const u8, []const u8, EnvNameHashContext, false);

    pub const Size = usize;

    pub const EnvNameHashContext = struct {
        pub fn hash(self: @This(), s: []const u8) u32 {
            _ = self;
            switch (native_os) {
                else => return std.array_hash_map.hashString(s),
                .windows => {
                    var h = std.hash.Wyhash.init(0);
                    var it = unicode.Wtf8View.initUnchecked(s).iterator();
                    while (it.nextCodepoint()) |cp| {
                        const cp_upper = if (std.math.cast(u16, cp)) |wtf16|
                            std.os.windows.toUpperWtf16(wtf16)
                        else
                            cp;
                        h.update(&[_]u8{
                            @truncate(cp_upper >> 0),
                            @truncate(cp_upper >> 8),
                            @truncate(cp_upper >> 16),
                        });
                    }
                    return @truncate(h.final());
                },
            }
        }

        pub fn eql(self: @This(), a: []const u8, b: []const u8, b_index: usize) bool {
            _ = self;
            _ = b_index;
            return eqlKeys(a, b);
        }
    };
    fn eqlKeys(a: []const u8, b: []const u8) bool {
        return switch (native_os) {
            else => std.array_hash_map.eqlString(a, b),
            .windows => std.os.windows.eqlIgnoreCaseWtf8(a, b),
        };
    }

    pub fn validateKeyForPut(key: []const u8) bool {
        switch (native_os) {
            else => return key.len > 0 and mem.findAny(u8, key, &.{ 0, '=' }) == null,
            .windows => {
                if (!unicode.wtf8ValidateSlice(key)) return false;
                return key.len > 0 and key[0] != 0 and mem.findAnyPos(u8, key, 1, &.{ 0, '=' }) == null;
            },
        }
    }

    pub fn validateKeyForFetch(key: []const u8) bool {
        if (native_os == .windows and !unicode.wtf8ValidateSlice(key)) return false;
        return true;
    }

    /// Create a Map backed by a specific allocator.
    /// That allocator will be used for both backing allocations
    /// and string deduplication.
    pub fn init(allocator: Allocator) Map {
        return .{ .array_hash_map = .empty, .allocator = allocator };
    }

    /// Free the backing storage of the map, as well as all
    /// of the stored keys and values.
    pub fn deinit(self: *Map) void {
        const gpa = self.allocator;
        for (self.keys()) |key| gpa.free(key);
        for (self.values()) |value| gpa.free(value);
        self.array_hash_map.deinit(gpa);
        self.* = undefined;
    }

    pub fn keys(map: *const Map) [][]const u8 {
        return map.array_hash_map.keys();
    }

    pub fn values(map: *const Map) [][]const u8 {
        return map.array_hash_map.values();
    }

    pub fn putPosixBlock(map: *Map, view: PosixBlock.View) Allocator.Error!void {
        for (view.slice) |entry| {
            var entry_i: usize = 0;
            while (entry[entry_i] != 0 and entry[entry_i] != '=') : (entry_i += 1) {}
            const key = entry[0..entry_i];

            var end_i: usize = entry_i;
            while (entry[end_i] != 0) : (end_i += 1) {}
            const value = entry[entry_i + 1 .. end_i];

            try map.put(key, value);
        }
    }

    pub fn putWindowsBlock(map: *Map, view: WindowsBlock.View) Allocator.Error!void {
        var i: usize = 0;
        while (view.ptr[i] != 0) {
            const key_start = i;

            // There are some special environment variables that start with =,
            // so we need a special case to not treat = as a key/value separator
            // if it's the first character.
            // https://devblogs.microsoft.com/oldnewthing/20100506-00/?p=14133
            if (view.ptr[key_start] == '=') i += 1;

            while (view.ptr[i] != 0 and view.ptr[i] != '=') : (i += 1) {}
            const key_w = view.ptr[key_start..i];
            const key = try unicode.wtf16LeToWtf8Alloc(map.allocator, key_w);
            errdefer map.allocator.free(key);

            if (view.ptr[i] == '=') i += 1;

            const value_start = i;
            while (view.ptr[i] != 0) : (i += 1) {}
            const value_w = view.ptr[value_start..i];
            const value = try unicode.wtf16LeToWtf8Alloc(map.allocator, value_w);
            errdefer map.allocator.free(value);

            i += 1; // skip over null byte

            try map.putMove(key, value);
        }
    }

    /// Same as `put` but the key and value become owned by the Map rather
    /// than being copied.
    /// If `putMove` fails, the ownership of key and value does not transfer.
    ///
    /// Asserts that `key` is valid:
    /// - It cannot contain a NUL (`'\x00') byte.
    /// - It must have a length > 0.
    /// - It cannot contain `=`, except on Windows where only the first code point is allowed to be `=`.
    /// - On Windows, it must be valid [WTF-8](https://wtf-8.codeberg.page/).
    pub fn putMove(self: *Map, key: []u8, value: []u8) Allocator.Error!void {
        assert(validateKeyForPut(key));
        const gpa = self.allocator;
        const get_or_put = try self.array_hash_map.getOrPut(gpa, key);
        if (get_or_put.found_existing) {
            gpa.free(get_or_put.key_ptr.*);
            gpa.free(get_or_put.value_ptr.*);
            get_or_put.key_ptr.* = key;
        }
        get_or_put.value_ptr.* = value;
    }

    /// `key` and `value` are copied into the Map.
    ///
    /// Asserts that `key` is valid:
    /// - It cannot contain a NUL (`'\x00') byte.
    /// - It must have a length > 0.
    /// - It cannot contain `=`, except on Windows where only the first code point is allowed to be `=`.
    /// - On Windows, it must be valid [WTF-8](https://wtf-8.codeberg.page/).
    pub fn put(self: *Map, key: []const u8, value: []const u8) Allocator.Error!void {
        assert(validateKeyForPut(key));
        const gpa = self.allocator;
        const value_copy = try gpa.dupe(u8, value);
        errdefer gpa.free(value_copy);
        const get_or_put = try self.array_hash_map.getOrPut(gpa, key);
        errdefer {
            if (!get_or_put.found_existing) assert(self.array_hash_map.pop() != null);
        }
        if (get_or_put.found_existing) {
            gpa.free(get_or_put.value_ptr.*);
        } else {
            get_or_put.key_ptr.* = try gpa.dupe(u8, key);
        }
        get_or_put.value_ptr.* = value_copy;
    }

    /// Find the address of the value associated with a key.
    /// The returned pointer is invalidated if the map resizes.
    /// On Windows, asserts that `key` is valid [WTF-8](https://wtf-8.codeberg.page/).
    pub fn getPtr(self: Map, key: []const u8) ?*[]const u8 {
        assert(validateKeyForFetch(key));
        return self.array_hash_map.getPtr(key);
    }

    /// Return the map's copy of the value associated with
    /// a key.  The returned string is invalidated if this
    /// key is removed from the map.
    /// On Windows, asserts that `key` is valid [WTF-8](https://wtf-8.codeberg.page/).
    pub fn get(self: Map, key: []const u8) ?[]const u8 {
        assert(validateKeyForFetch(key));
        return self.array_hash_map.get(key);
    }

    /// On Windows, asserts that `key` is valid [WTF-8](https://wtf-8.codeberg.page/).
    pub fn contains(m: *const Map, key: []const u8) bool {
        assert(validateKeyForFetch(key));
        return m.array_hash_map.contains(key);
    }

    /// If there is an entry with a matching key, it is deleted from the hash
    /// map. The entry is removed from the underlying array by swapping it with
    /// the last element.
    ///
    /// Returns true if an entry was removed, false otherwise.
    ///
    /// This invalidates the value returned by get() for this key.
    /// On Windows, asserts that `key` is valid [WTF-8](https://wtf-8.codeberg.page/).
    pub fn swapRemove(self: *Map, key: []const u8) bool {
        assert(validateKeyForFetch(key));
        const kv = self.array_hash_map.fetchSwapRemove(key) orelse return false;
        const gpa = self.allocator;
        gpa.free(kv.key);
        gpa.free(kv.value);
        return true;
    }

    /// If there is an entry with a matching key, it is deleted from the map.
    /// The entry is removed from the underlying array by shifting all elements
    /// forward, thereby maintaining the current ordering.
    ///
    /// Returns true if an entry was removed, false otherwise.
    ///
    /// This invalidates the value returned by get() for this key.
    /// On Windows, asserts that `key` is valid [WTF-8](https://wtf-8.codeberg.page/).
    pub fn orderedRemove(self: *Map, key: []const u8) bool {
        assert(validateKeyForFetch(key));
        const kv = self.array_hash_map.fetchOrderedRemove(key) orelse return false;
        const gpa = self.allocator;
        gpa.free(kv.key);
        gpa.free(kv.value);
        return true;
    }

    /// Returns the number of KV pairs stored in the map.
    pub fn count(self: Map) Size {
        return self.array_hash_map.count();
    }

    /// Returns an iterator over entries in the map.
    pub fn iterator(self: *const Map) ArrayHashMap.Iterator {
        return self.array_hash_map.iterator();
    }

    /// Returns a full copy of `em` allocated with `gpa`, which is not necessarily
    /// the same allocator used to allocate `em`.
    pub fn clone(m: *const Map, gpa: Allocator) Allocator.Error!Map {
        var new: Map = .init(gpa);
        errdefer new.deinit();
        try new.array_hash_map.ensureUnusedCapacity(gpa, m.array_hash_map.count());
        for (m.array_hash_map.keys(), m.array_hash_map.values()) |key, value| {
            try new.put(key, value);
        }
        return new;
    }

    /// Adds all the key-value pairs from `other` into this `m`.
    pub fn putAll(m: *Map, other: *const Map) Allocator.Error!void {
        const gpa = m.allocator;
        try m.array_hash_map.ensureUnusedCapacity(gpa, other.array_hash_map.count());
        const start = m.count();
        errdefer while (m.array_hash_map.count() > start) {
            const kv = m.array_hash_map.pop().?;
            gpa.free(kv.key);
            gpa.free(kv.value);
        };
        for (other.array_hash_map.keys(), other.array_hash_map.values()) |key, value| {
            try m.put(key, value);
        }
    }

    /// Set the length to zero, freeing all key and value memory, not freeing
    /// the allocation for the entries.
    pub fn clearRetainingCapacity(m: *Map) void {
        const gpa = m.allocator;
        for (m.array_hash_map.keys(), m.array_hash_map.values()) |k, v| {
            gpa.free(k);
            gpa.free(v);
        }
        m.array_hash_map.clearRetainingCapacity();
    }

    /// Creates a null-delimited environment variable block in the format
    /// expected by POSIX, from a hash map plus options.
    pub fn createPosixBlock(
        map: *const Map,
        gpa: Allocator,
        options: CreatePosixBlockOptions,
    ) Allocator.Error!PosixBlock {
        const ZigProgressAction = enum { nothing, edit, delete, add };
        const zig_progress_action: ZigProgressAction = action: {
            const fd = options.zig_progress_fd orelse break :action .nothing;
            const exists = map.contains("ZIG_PROGRESS");
            if (fd >= 0) {
                break :action if (exists) .edit else .add;
            } else {
                if (exists) break :action .delete;
            }
            break :action .nothing;
        };

        const envp = try gpa.allocSentinel(?[*:0]u8, len: {
            var len: usize = map.count();
            switch (zig_progress_action) {
                .add => len += 1,
                .delete => len -= 1,
                .nothing, .edit => {},
            }
            break :len len;
        }, null);
        var envp_len: usize = 0;
        errdefer {
            envp[envp_len] = null;
            PosixBlock.deinit(.{ .slice = envp[0..envp_len :null] }, gpa);
        }

        if (zig_progress_action == .add) {
            envp[envp_len] = try std.fmt.allocPrintSentinel(gpa, "ZIG_PROGRESS={d}", .{options.zig_progress_fd.?}, 0);
            envp_len += 1;
        }

        for (map.keys(), map.values()) |key, value| {
            if (mem.eql(u8, key, "ZIG_PROGRESS")) switch (zig_progress_action) {
                .add => unreachable,
                .delete => continue,
                .edit => {
                    envp[envp_len] = try std.fmt.allocPrintSentinel(gpa, "{s}={d}", .{
                        key, options.zig_progress_fd.?,
                    }, 0);
                    envp_len += 1;
                    continue;
                },
                .nothing => {},
            };

            envp[envp_len] = try std.fmt.allocPrintSentinel(gpa, "{s}={s}", .{ key, value }, 0);
            envp_len += 1;
        }

        assert(envp_len == envp.len);
        return .{ .slice = envp };
    }

    /// Caller owns result.
    pub fn createWindowsBlock(
        map: *const Map,
        gpa: Allocator,
        options: CreateWindowsBlockOptions,
    ) error{ OutOfMemory, InvalidWtf8 }!WindowsBlock {
        // count bytes needed
        const max_chars_needed = max_chars_needed: {
            var max_chars_needed: usize = "\x00".len;
            if (options.zig_progress_handle) |handle| if (handle != std.os.windows.INVALID_HANDLE_VALUE) {
                max_chars_needed += std.fmt.count("ZIG_PROGRESS={d}\x00", .{@intFromPtr(handle)});
            };
            for (map.keys(), map.values()) |key, value| {
                if (options.zig_progress_handle != null and eqlKeys(key, "ZIG_PROGRESS")) continue;
                max_chars_needed += key.len + "=".len + value.len + "\x00".len;
            }
            break :max_chars_needed @max("\x00\x00".len, max_chars_needed);
        };
        const block = try gpa.alloc(u16, max_chars_needed);
        errdefer gpa.free(block);

        var i: usize = 0;
        if (options.zig_progress_handle) |handle| if (handle != std.os.windows.INVALID_HANDLE_VALUE) {
            @memcpy(
                block[i..][0.."ZIG_PROGRESS=".len],
                &[_]u16{ 'Z', 'I', 'G', '_', 'P', 'R', 'O', 'G', 'R', 'E', 'S', 'S', '=' },
            );
            i += "ZIG_PROGRESS=".len;
            var value_buf: [std.fmt.count("{d}", .{std.math.maxInt(usize)})]u8 = undefined;
            const value = std.fmt.bufPrint(&value_buf, "{d}", .{@intFromPtr(handle)}) catch unreachable;
            for (block[i..][0..value.len], value) |*r, v| r.* = v;
            i += value.len;
            block[i] = 0;
            i += 1;
        };
        for (map.keys(), map.values()) |key, value| {
            if (options.zig_progress_handle != null and eqlKeys(key, "ZIG_PROGRESS")) continue;
            i += try unicode.wtf8ToWtf16Le(block[i..], key);
            block[i] = '=';
            i += 1;
            i += try unicode.wtf8ToWtf16Le(block[i..], value);
            block[i] = 0;
            i += 1;
        }
        // An empty environment is a special case that requires a redundant
        // NUL terminator. CreateProcess will read the second code unit even
        // though theoretically the first should be enough to recognize that the
        // environment is empty (see https://nullprogram.com/blog/2023/08/23/)
        for (0..2) |_| {
            block[i] = 0;
            i += 1;
            if (i >= 2) break;
        } else unreachable;
        const reallocated = try gpa.realloc(block, i);
        return .{ .slice = reallocated[0 .. i - 1 :0] };
    }
}