Tiny bit of confusion around Zig's standardized naming conventions

Hi,

Loving Zig so far! As a Rustacean it took me a while to get used to way different safety rails, but I enjoy actually being able to read the code of the standard library… or what my coworker spits out…

Zig has quite an interesting convention for names. It’s grown on me, but I’m personally used to snake_case everywhere minus for types and enum fields, like Rust has. That said, there’s one part that still confuses me. The rule for enums is to use TitleCase for the enum name, and snake_case for its enum values. This makes perfect sense. However, why does this not apply to errors? The error set type is written in TitleCase, but also all of the error values are TitleCase, not snake_case, like enum values. Why is this? Is each unique error introduced into a set considered to technically be a new type, while enum fields aren’t, because they’re all a part of the same type?

Thanks!

10 Likes

I’m not sure on the exact logic, but my guess is something about each error being a unique value.

The closest analog in zig being types, types are unique values identified by their (fully qualified) name.

The difference though is that you never define an error, you only reference its name, if it doesn’t exist then zig implicitly creates it.

You could argue that description applies to built-in types like integers, pointers, slices, arrays, etc.

3 Likes

Although I abandoned Rust, I stuck to the snake case for functions. Easier on the eye. Starting functions with lowercase and thenContinueWithCamels is a very strange thing in my eyes. It would be very interesting to know who invented that.
Not sure myself yet what the most readable style for enum values is.

3 Likes

I felt the same way about the camelCase functions until I discovered @TypeOf, std.ArrayList, etc. Functions aren’t just one case in Zig.

In Zig, a common convention is that any function returning a type is effectively a generic data structure. @TypeOf and std.ArrayList are good examples: they take a type as a parameter and produce a new type based on it. You can think of std.ArrayList(T) as the Zig equivalent of Vec<T> in Rust. Viewing it this way makes the design easier to understand.

3 Likes

Yep, once I learned that functions returning newtypes use TitleCase, camelCase for everything else made more sense.

1 Like

Ah yes, a fellow rebel. I got MicroZig to adopt snake case functions, so much better to look at.

4 Likes

As someone who much prefers snake_case for function names, I find it kind of concerning that a lot of people use it instead of the standard Zig convention… One of the nice things about modern languages is that they define strong naming conventions on inception and then people generally follow them.

Also since Zig is a one-namespace language there might be potential conflicts between fields and function names, say if you want to implement a static interface with a function name foo_bar but you already have a field foo_bar. It’s probably rare in practice though (and for one-word names it may conflict regardless of naming convention).

8 Likes

Yes. That is logical. I like CamelCase typenames otherwise code would become too flat.

So that is where I make my special Zig exceptions.

pub fn MovePicker(comptime gentype: GenType, comptime us: Color) type {
}

Wait what? I might be misunderstanding you. In Zig the only way to pollute one namespace with another is @usingnamespace. Apart from that, every struct/file is its own namespace, no?

Yeah you are misunderstanding what I meant, but I guess I wasn’t super clear.

Some languages have separate namespaces for different “kinds” of names (e.g. field names and function names. See this Rust example. And then if you want to call a function that is stored in a field it has a separate syntax with extra parentheses: (s.foo)(args…).

2 Likes

Got it, thanks! I really had no idea :grin:

A note on terminology:

PascalCase

camelCase

snake_case

kebab-case

lower case

UPPER CASE

Title Case

7 Likes

Examples of multiple namespaces in dupdrop’s sense:

In “Lisp-2” languages, functions and variables are in separate symbol tables so you can have a function foo and a variable foo and they are different things. In “Lisp-1” languages there’s just one symbol table for both functions and variables.

In C, tags (e.g., struct foo, enum foo) are in a separate symbol table from functions and variables, as are labels.

In Rust, there are separate symbol tables for values (variables and functions), types/traits, macros, lifetimes, and labels.

[edited] In Zig, there’s a separate symbol table for labels (so it’s not actually a one-namespace language, but labels are always distinguished syntactically by the preceding or following colon, so they aren’t relevant to dupdrop’s point about conflicts).

[I’m using “symbol table” in an abstract sense–I mean a namespace but wanted to avoid the confusion about that term. In practice an implementation might use just one symbol table but with a field to distinguish between different kinds of symbols.]

4 Likes

1963 ASCII had in the place of ^ and in the place of _.
Smalltalk (and some other languages) adopted as assignment operator and camel case was used for word separation (before that camel case was used in medicine terms). After the change in ASCII both the assignment operator and the return operator remained _ and ^. C++ and Java programmers popularized camel case by copying names from Smalltalk libraries.

1 Like

What do you mean?

test "label and variable in the same namespace" {
    const str: []const u8 = "hello world";
    const has_a = str: for (str) |ch| {
        if (ch == 'a') break :str true;
    } else false;
    try std.testing.expect(!has_a);
}
4 Likes