2 ms·
In general, the style the author has adopted is to introduce brevity where he can, and use wrappers over what would have been standard and idiomatic C code. In
by lelanthran 3y ago
In general, the style the author has adopted is to introduce brevity where he can, and use wrappers over what would have been standard and idiomatic C code. In most situations, these conventions aren't good in a non-solo project, because they simply aren't as obvious to the programmer.
He says so himself:
> I don’t intend to use these names in isolation, such as in code snippets (outside of this article). If I did, examples would require the typedefs to give readers the complete context. That’s not worth extra explanation. Even in the most recent articles I’ve used ptrdiff_t instead of size.
You require extra work to understand his basic types before reading even a short snippet, so he doesn't use it when he wants people to read short snippets.
Introducing additional stuff the programmer must remember that does not add any safety is pointless busywork.
A non-complete summary of his conventions:
1. typedef standard typenames to 3-char symbols,
2. remove qualifiers like const,
3. use macros to reduce the amount of typing the programmer does,
4. typedef all structs (and enums too, I assume)
5. A macro-ized string-typed with prefixed-length.
> Starting with the fundamentals, I’ve been using short names for primitive types. The resulting clarity was more than I had expected,
This isn't clear: `int8_t` is a lot clearer to a C programmer than `i8`, because a C programmer has already internalised the pattern of the stdint.h types. This is going to lead to subtle bugs as well: quick, according to his convention, what is the % specifier for `byte`?
You can use %c, but that gives you an ascii character (which is not what we think of when we say 'byte').
If you use PRIu8 the compiler might give warnings because `char` might be signed. The best option is to just not use `byte` and use `uint8_t` instead (or, in his system, `u8`).
Same with `b32` vs `i32` - it's a distinction without a difference and mixing these types won't give compiler warnings, while it is almost certainly an error on the part of the developer. Use `bool` if you don't like `_Bool`.
In general I try to take advantage of whatever typing C provides; I don't try to subvert it because I want the compiler to warn me when my intention doesn't match the code I wrote.
> No const. It serves no practical role in optimization, and I cannot recall an instance where it caught, or would have caught, a mistake.
I disagree with dropping `const`.
1. It's useful as an indicator to the caller that the returned value must/must not be freed. It's a convention I use that makes it easy to visually spot memory leaks.
Of the two functions below, it's clear to me which one needs the returned value `free()`ed and which one doesn't.
const char *replace_substring (const char *src, const char *pat, const char *replacement);
char *replace_substring (const char *src, const char *pat, const char *replacement);
2. It actually does catch a lot of problems, because the compiler warns me when I attempt to modify a value that some other code I wrote never intended to be modified. It's about intention, and when I know it is safe to modify the `const` value, then I have to explicitly cast away the const to compile my program. Anyone reading the program will know that the modification of the const-qualifed value is intentional, and therefore safe.
> #define s8(s) (s8){(u8 *)s, lengthof(s)}
This is interesting. I will try this out in my next project. I do think that there'll be quite a few compiler warnings for sign-mismatch though. This is the second "I wonder what the sign is" question for programmers reading his code - it means that his code has to compile with the flags that he compiles it with (I assume he's passing a flag to force chars to a particular sign). You can't simply compile his code in another project unless you copy his flags, and those flags may conflict with the new projects flags.
I also wish that he'd showed a few examples of how having the length helps - what is presented in the post doesn't show any additional string safety over using nul-terminated strings. All those macros, including the one that creates the struct, could be written to operate on null-terminated strings. In essence, the length can be simply unused for everything! Where's the safety!?
> It’s also led to a style of defining a zero-initialized return value at the top of the function, i.e. ok is false, and then use it for all return statements. On error, it can bail out with an immediate return.
I use a similar pattern, but I use `goto cleanup` on all errors; you can't, as a general pattern, return early in a non-trivial C function without leaking resources. You can, as a general pattern, `goto cleanup` in every C function to clean up resources. I prefer the general pattern that I use everywhere rather than having to ensure that all resources acquired up to that particular return statement are released.
> rather than include windows.h, write the prototypes out by hand using custom types.
I think this is a very bad idea: you can't depend on the headers not changing after a compiler or library update. Sure, maybe in practice, all the Windows types and declarations don't change all that much, but I wouldn't want to be the developer trying to hunt down a bug because the interface to some function has changed and the compiler isn't giving me errors.
All in all, I dunno if I would look forward to working on a team with these conventions - the code is harder to read, doesn't work in isolation, needs custom flags, and introduces a string type without introducing any string safety with it.