Here I'm assuming 32-bit values. In C, with relatively little support for generics, you can consider making multiple versions, possibly using _Generic (note, I haven't evaluated the sanity of using _Generic).
Alternatively you can use a #define. However, you need to use "mask" twice, so that gets tricky - either it requires care to keep the corresponding expression at the call site side-effect free. Or the macro needs to be written using compiler extensions like statement expressions and typeof() variable declarations à la Linux kernel.
The nice thing about this is that it's simple and works pretty much in any language so there's very little cognitive strain if you work with multiple languages at the same time.
I’ve definitely desired an operator that combines these before. I’ve settled on &== as the best syntax in C-style languages, with a &== b being equivalent to a & b == b (but with a single evaluation of b).
if (mask &== WGPUColorWriteMask_Alpha|WGPUColorWriteMask_Blue) { … }
I find only two very minor problems with it: firstly, that it’s not commutative (that is, a &== b is not equivalent to b &== a). Secondly, that it can be confused with the bitwise-and assignment operator &= (a &= b being equivalent to a = a & b for singly-evaluated lvalue a).
I’d then add |== for consistency and because it is conceivably useful. ^== is tempting, but since a ^== b would be just another spelling of a == 0, I’d skip it.
Faszinating: I also thought about exactly '&==' in the past. It seems to be natural choice for this.
But then, continuing that thinking, if you have '&==', you'd also need '&!=' -- that looks really confusing.
Can't we turn that into a '==0' test somehow? Maybe '^&', because '((a ^ b) & b) == 0' is equivalent to '(a & b) == b'. And, as somehow said: '~&' also works: '(~a & b) == 0' is also equivalent to '(a & b) == b'.
if ((a ~& b) == 0) { ... }
Wait -- we can swap that into '&~' and we're back in C. However, it reverses the logical order, with the test bits first:
if ((b & ~a) == 0) { ... }
if (((WGPUColorWriteMask_Alpha|WGPUColorWriteMask_Blue) & ~mask) == 0) {
...
}
So we're back -- this is standard C now. But I find it incomprehensible.
Now I’m trying to decide if you make it better or worse by replacing the == 0 with simple logical negation:
if (!(b & ~a)) { … }
if (!((WGPUColorWriteMask_Alpha|WGPUColorWriteMask_Blue) & ~mask)) { … }
if (!(~a & b)) { … }
if (!(~mask & (WGPUColorWriteMask_Alpha|WGPUColorWriteMask_Blue))) { … }
Actually, that latter makes a little more intuitive sense to me because of how ! and ~ are both negation¹, so they kinda cancel out and leave just the masking. Kinda.
—⁂—
¹ Fun fact: Rust uses ! for both logical and bitwise negation, backed by core::ops::Not, with bool → bool and {integer} → {integer}, since bool is a proper type and there’s no boolean coercion anywhere—so you would have to stick with `== 0` in Rust, though in practice you’d probably go all typey with the bitflags crate’s macro to generate good types with some handy extra methods, and write `mask.contains(WgpuColorWriteMask::Alpha | WgpuColorWriteMask::Blue)`.
// To check for either flag:
if mask.contains(WgpuColorWriteMask::Alpha) || mask.contains(WgpuColorWriteMask::Blue) { … }
if mask.intersects(WgpuColorWriteMask::Alpha | WgpuColorWriteMask::Blue) { … }
// To check for both flags:
if mask.contains(WgpuColorWriteMask::Alpha | WgpuColorWriteMask::Blue) { … }
Of common operators, / and - are the only ones that aren’t commutative.
For equality comparisons, there are two opposing conventions: actual == expected (the more popular, in my experience), and expected == actual (by no means rare). Having &== and |== be order-sensitive (though there’s absolutely no question in my mind about what the ordering should be) is mildly unfortunate.
Yeah ok. I'd consider that just a thing you gotta know. Like how for certain floating point values (a / b) * (c / d) can be fine while (a * c) / (b * d) gives you +/- infinity.
It's a separate operator after all, one I also miss a lot...
If you are willing to assume that the masks each only have 1 bit set, this would be less of a mouthful although it would draw other objections:
if (@popCount(mask & (WGPUColorWriteMask_Alpha|WGPUColorWriteMask_Blue)) == 2)
I do not know Zig, so the syntax might not be right. I did check to see that it has popcount [1].
If it has some concise way to flip all the bits, then this would be another possibility that isn't too verbose, but might raise other objections. Let fmask be mask with all the bits flipped (how would one do that in Zig?).
if ((fmask & (WGPUColorWriteMask_Alpha|WGPUColorWriteMask_Blue)) == 0)
Indeed, you would need to check that the mask ends up being exactly alpha and blue, otherwise it's just an automatic boolean conversion from integer to boolean, which many languages are doing away with due to all the bugs it produces.
A statically typed language shouldn't indulge in truthiness, no. But a cast is isn't truthiness, it's truth: the meaning of "make this int impenetrable to all but truth and falsehood" is in practice well defined, there's nothing implicit happening here.
The compiler isn't going to make that function, it's going to optimize back to a cast to boolean. Why make the poor shmoe user type it out?
Cast int8 to int16 isn't the same thing as int to boolean. In the first case, you get the same number (ignoring wrapping issues). In the second case, you're asking the compiler to make an arbitrary decision about what numbers go to what booleans.
There is nothing arbitrary whatsoever about the conversion of any string of bits to a boolean, it is false iff none of the bits are 1.
If you want to call it arbitrary, it's been arbitrated decades ago, but Boolean logic is much older than computers and works as it does for a reason. I suspect you know that.
the number 9, is not the same thing as "true". some people may want it to be, it might be convenient for some people if a compiler treats them as the same thing, but they aren't. In my opinion, its an anti-pattern to have the compiler make these type of decisions. Instead of writing and reading code that is explicit, you're writing an abbreviated version of what you want, and assuming that the compiler will know what you mean.
I think I made my point known. Converting int to boolean is not a settled task, as different people might not agree on what the correct way is to do that. False could be zero, or negative one, or any negative number, or even one. So its better not to have the compiler make that decision, and just have the code be explicit about what its trying to do.
"WGPUColorWriteMask_Alpha|WGPUColorWriteMask_Blue" creates a number with both the alpha and blue bits set, then "mask &" (that number) ands the mask with that number in which both bits are set, so it'll only return true if both alpha and blue are set. The parenthetical grouping of the operators is key here.
No it won't. & is "binary and" and result will be WGPUColorWriteMask_Alpha or WGPUColorWriteMask_Blue if only one of them is set. Which is non zero so evaluated to true. So it checks either or those flags
I believe it would need to be something like
if (mask & (WGPUColorWriteMask_Alpha|WGPUColorWriteMask_Blue) == (WGPUColorWriteMask_Alpha|WGPUColorWriteMask_Blue))
for both bits to be set here.
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.
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.
Doesn't this give you if alpha OR blue is set?
Errors like this are another reason syntactical sugar for readability is important.