Live data from Hacker News

Packed structs in Zig make bit/flag sets trivial

devlog.hexops.com

61–70 of 147 posts

Re: Packed structs in Zig make bit/flag sets trivial

#61
post #44

> This all works, people have been doing it for years in C, C++, Java, Rust, and more. In Zig, we can do better. We can also do better in those other languages, too. For example, in Rust, I can use a crate like `bitfield` which gives me a macro with which I can write bitfield! { pub struct Color(u32); red, set_red: 0; green, set_green: 1; blue, set_blue: 2; alpha, set_alpha: 3; } Don't get me wrong: it's cool that fu…

Is it something I'd ever want to rely on third-party functionality for?

C has them and their implementation seems to be universally disliked. Using an external crate with an implementation that people do like, with the possibility to substitute another if you disagree, seems better than to force a specific implementation into the language (that people will then replace with external dependencies or that people will learn to avoid as a concept).

Re: Packed structs in Zig make bit/flag sets trivial

#62
post #48

Earlier quoted context omitted.

That's the point of a packed struct: to explicitly declare the physical layout (instead of letting the compiler optimize it). If a packed struct ends up being non-portable between Zig implementations, then my impression is that one and/or the other must have a bug that must be fixed in order to actually comply with the language spec.

It is not possible to specify bitfields in a way that makes sense, is consistent with a bytewise view of memory, and is portable between big endian and little endian processors. Say you have, for instance (using C notation) struct { unsigned one : 8; unsigned two : 8; }; The fields are supposed to be represented in memory in the same order they are declared, so one is the first byte and two is the second byte. This s…

> It is not possible to specify bitfields in a way that makes sense, is consistent with a bytewise view of memory, and is portable between big endian and little endian processors.

I'm not sure I accept "consistency with a bytewise view of memory" as a well-defined, reasonable concept. I do expect to give a list of bit widths, and get a field that has these in consecutive order. Why would it randomly do weird things on an 8-bit boundary?

> If the fields are allocated in order, is u4 the low bits of the first byte or the high bits?

It's the low bits on LE, and the high bits on BE.

> If you require it to be the low bits, then it works ok on a little endian machine, but on a big endian machine the uC field ends up split

That's why the direction is defined to match the endianness; you get a consecutive chain of bits in either case.

> so the 16 bit view looks like: CCCC4444CCCCCCCC

It's 4444CCCCCCCCCCCC on BE, and CCCCCCCCCCCC4444 on LE. If you need something else, it's no longer a question of defining an ABI-consistent structure, but rather expressing a representation of an externally given constraint.

Re: Packed structs in Zig make bit/flag sets trivial

#63
post #7

if (mask & (WGPUColorWriteMask_Alpha|WGPUColorWriteMask_Blue)) { // alpha and blue are set.. } Doesn't this give you if alpha OR blue is set? Errors like this are another reason syntactical sugar for readability is important.

Yes it would need to be: if ((mask & (WGPUColorWriteMask_Alpha|WGPUColorWriteMask_Blue)) == (WGPUColorWriteMask_Alpha|WGPUColorWriteMask_Blue)) { ... } ...which is quite a mouthful.

You can also do it as follows:

    if (!(~mask & (WGPUColorWriteMask_Alpha|WGPUColorWriteMask_Blue))) { ... }
Not quite as long but perhaps less readable.

Re: Packed structs in Zig make bit/flag sets trivial

#64
post #24
post #6

Earlier quoted context omitted.

Admittedly I didn't know C had bit fields like that, so I didn't cover them. I've never seen them used in practice and I've used quite a lot of C libraries. I wonder why that is? One major difference appears to be that C bitfields memory layout is compiler-dependant. The other major difference is Zig's arbitrary-bit-width integer types just leading to less footguns I would speculate

Far as I can tell 'don't use bit fields' is something students learn from their CS professors along with don't use goto, multiple returns, continue statements, pass structs by value, and always convert to big endian when sending data over a wire. My experience is C compilers have ways packing and defining the order of bitfields and structs.

Most, even all of these, sound to me like cargo-culting. I certainly use multiple returns and continue statements all the time. I pass structs by value when I want to stress that they represent a value type that can / should be copied around (for example, a slice). As for the format to send data over a wire, I will either use the ntoh() / hton() macros, or move to a more standard binary format such as protobuffs.

The only thing I don't usually run into is the very topic of this article: bit fields.

Re: Packed structs in Zig make bit/flag sets trivial

#65
post #62
post #48

Earlier quoted context omitted.

It is not possible to specify bitfields in a way that makes sense, is consistent with a bytewise view of memory, and is portable between big endian and little endian processors. Say you have, for instance (using C notation) struct { unsigned one : 8; unsigned two : 8; }; The fields are supposed to be represented in memory in the same order they are declared, so one is the first byte and two is the second byte. This s…

> It is not possible to specify bitfields in a way that makes sense, is consistent with a bytewise view of memory, and is portable between big endian and little endian processors. I'm not sure I accept "consistency with a bytewise view of memory" as a well-defined, reasonable concept. I do expect to give a list of bit widths, and get a field that has these in consecutive order. Why would it randomly do weird things o…

> > If the fields are allocated in order, is u4 the low bits of the first byte or the high bits?

> It's the low bits on LE, and the high bits on BE.

You are advocating for the current rule in C. As I said, C's rule implies 8 bit fields will be in different orders in memory on machines of different endianness, which makes it very difficult to use bitfields to get exact control over memory layout in a portable manner.

Re: Packed structs in Zig make bit/flag sets trivial

#66
post #57

Earlier quoted context omitted.

I thought you were not able to initialize bitfield members like that in struct. Am I misremembering, was that something else or is it compiler/c++ release dependent?

You can absolutely initialize bitfield members in structs, not sure where your memory is from… maybe it was broken on some specific compiler vendors/versions?

Possible they were just pre-C++11

Re: Packed structs in Zig make bit/flag sets trivial

#67
post #7

if (mask & (WGPUColorWriteMask_Alpha|WGPUColorWriteMask_Blue)) { // alpha and blue are set.. } Doesn't this give you if alpha OR blue is set? Errors like this are another reason syntactical sugar for readability is important.

That's a 'user error'. "Nothing wrong with the language"

Re: Packed structs in Zig make bit/flag sets trivial

#68
post #22

Earlier quoted context omitted.

Here is a sample of how you do this in C++ https://godbolt.org/z/W3bMcqM5P

Wow... I thought I had seen a lot of strange C++ code but I have _never_ seen packed structs using this syntax: struct X { type alias : numeric_value = false; ... } I love the power of C++ but there is _so much_ to the language. I'm sure there would also be some template meta programming solution even if this syntax was available.

It's probably tough to find a metaprogramming solution to this, for two reasons:

1. Addressing happens at the byte level, not the bit level, so a type can't begin on any bit. You'd have to do your own addressing, such as in std::bitset.

2. For now, there's no reflection in the language, so you can't really assign names to members in a general way (hello, preprocessor). A solution might be to index by type; something like the following:

struct BitA{}; struct BitB{};

using ExampleBitFields = BitFields;

bool checkBitA(const ExampleBitFields& x) { return x[BitA{}]; }

However, this has a lot of downsides.

Re: Packed structs in Zig make bit/flag sets trivial

#69
post #7

if (mask & (WGPUColorWriteMask_Alpha|WGPUColorWriteMask_Blue)) { // alpha and blue are set.. } Doesn't this give you if alpha OR blue is set? Errors like this are another reason syntactical sugar for readability is important.

That's a 'user error'. "Nothing wrong with the language"

That's a user error because there very much is something wrong with the language. The error is that it deals in truthy and falsy rather than a strict true^false dichotomy. So 1 is just as truthy as 2 is. And -234123 for that matter.

Re: Packed structs in Zig make bit/flag sets trivial

#70
post #7

if (mask & (WGPUColorWriteMask_Alpha|WGPUColorWriteMask_Blue)) { // alpha and blue are set.. } Doesn't this give you if alpha OR blue is set? Errors like this are another reason syntactical sugar for readability is important.

That's a 'user error'. "Nothing wrong with the language"

If that’s user error, then there’s nothing wrong with C, C++, Java, JavaScript and myriad other languages. Then we don’t need any new ones on the basis of readability, elegance, expressiveness and myriad other subjective properties. Yet many discuss these aspects primarily.
Post reply on HN