Zig API naming: single-letter generics with no explanation?

This is somewhat theoretical, using HashMap as an example, but the underlying concern is about readability when trying to understand less-used parts of std.

Zig std API signature as shown via LSP:

pub fn ArrayHashMapWithAllocator(
    comptime K: type,
    comptime V: type,
    /// A namespace that provides these two functions:
    /// * `pub fn hash(self, K) u32`
    /// * `pub fn eql(self, K, K, usize) bool`
    ///
    /// The final `usize` in the `eql` function represents the index of the key
    /// that's already inside the map.
    comptime Context: type,
    /// When `false`, this data structure is biased towards cheap `eql`
    /// functions and avoids storing each key's hash in the table. Setting
    /// `store_hash` to `true` incurs more memory cost but limits `eql` to
    /// being called only once per insertion/deletion (provided there are no
    /// hash collisions).
    comptime store_hash: bool,
)

I came across ArrayHashMap, and when I read the API signature, the K and V caused me to stutter. I am still new to Zig, and I am not fast at deducing meaning from single letters. I can eventually infer it here because it is a hash map, but I had to stop and think.

The main problem for me was that there is no direct way in the signature to deduce what K and V are. I tried to find this information in the comment below:

    /// A namespace that provides these two functions:
    /// * `pub fn hash(self, K) u32`
    /// * `pub fn eql(self, K, K, usize) bool`
    ///
    /// The final `usize` in the `eql` function represents the index of the key
    /// that's already inside the map.

But this does not actually tell you what K or V represent. It explains usage and constraints, but not the roles of the types themselves.

So in my mind this causes multiple problems:

  1. If I want to search for hash maps to read up on them, I cannot easily search for something like “hash map key” or “hash map value” based on the API. Searching for “K and V in HashMap” is noisy and assumes I already know what those letters mean.

  2. If I go into the source, there is still no comment explaining what K and V are, and I now have to deduce it from even more code that also uses single-letter names. At that point I am effectively reverse-engineering the structure. That can be a good learning exercise in Zig, but it is not a fast way to understand how to use a standard library function and then move on.

  3. This is a hash map, so I can lean on prior knowledge to infer the meaning. My concern is about lesser-known parts of std. If they follow the same naming convention, the initial confusion will be worse, because I do not already know what the structure is supposed to represent.

  4. Point 3 can be somewhat mitigated if std treats K and V as stable conventions, where K always means “key” and V always means “value”. Once learned, that makes signatures readable. But this feels fragile if the same letters are ever reused with different meanings (for example K meaning “keyword” in some other API). In that case the reader may assume the wrong meaning based on habit.

To address this, I see two possible improvements.

One is to keep the short names but add a brief comment in the signature:

pub fn ArrayHashMapWithAllocator(
    comptime K: type, /// Key
    comptime V: type, /// Value
    ...
)

Another is to spell the names out directly:

pub fn ArrayHashMapWithAllocator(
    comptime Key: type, 
    comptime Value: type, 
    ...
)

My main question is whether there is a strong rationale for using single-letter type parameter names in Zig std public APIs, instead of either spelling them out or adding short clarifying comments at the declaration site.

It is common between programming languages to use single letter names for type parameters.
Rust also uses K and V for HashMap.
C++ map calls the parameters Key and T (It is a surprise for me that C++ calls the key type Key and not that they call the value type T).

Indeed, a documentation comment is missing for K and V.

5 Likes

While I agree Key and Value might be more helpful and strict adherence to best practices, I think this is just one of those shortcuts that are common enough in the programming world where the “helpful names” rule gets bent, similar to how we all often use i in for loops, or T for a generic type, etc.

There is probably not going to be any great argument why it shouldn’t be Key and Value, but likewise I doubt you will find many who take great issue with it, even though it indeed is not very descriptive.

4 Likes