Hacker News new | past | comments | ask | show | jobs | submit login

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.




I’m seeing a lot of versions of the correct logic under this comment, so here’s mine!

    if((mask & WGPUColorWriteMask_Alpha) && (mask & WGPUColorWriteMask_Blue)) { //… }
This is the least confusing form I’ve seen that doesn’t require a function, macro, custom operator, etc.


You don't need syntactical sugar. Just write a function.

    static bool all_bits_set(i32 value, i32 mask) { return (value & mask) == mask; }
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.


Yeah the usual way in C would be to use statement expression (which is a GCC extension but is supported by at least clang and msvc)

    #define all_bits_set(value, mask) ({__typeof__(value) v = (value), m = (mask); (v & m) == m; })
I personally would go for the extension aboveor a single 64 bit function, but here's the _Generic version:

    static bool all_bits_set32(i32 value, i32 mask) { return (value & mask) == mask; }
    static bool all_bits_set64(i64 value, i64 mask) { return (value & mask) == mask; }
    #define all_bits_set(value, mask) _Generic((value), int32_t: all_bits_set32, int64_t: all_bits_set64)(value, mask)


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.


Yes it would need to be:

    if ((mask & (WGPUColorWriteMask_Alpha|WGPUColorWriteMask_Blue)) == (WGPUColorWriteMask_Alpha|WGPUColorWriteMask_Blue)) { ... }
...which is quite a mouthful.


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)`.


You could also play the game of pretending that the scalar is an object and allow for syntactical sugar like:

    if (mask.contains(WGPUColorWriteMask_Alpha) || mask.contains(WGPUColorWriteMask_Blue)) { ... }


Fun fact: Rust’s bitflags macro crate generates just this, https://docs.rs/bitflags/latest/bitflags/example_generated/s....

  // 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) { … }


> I find only two very minor problems with it: firstly, that it’s not commutative

Why is that a problem? That is, if a has additional bits set compared to b, then ((a & b) == b) != ((b & a) == a), no?


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.

It’s very minor.


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...


You can also do it as follows:

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


I use:

    if (all_of(mask, WGPUColorWriteMask_Alpha | WGPUColorWriteMask_Blue))
with all_of() being a #define. Likewise none_of(), any_of().

No need for special operators.


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)
[1] https://ziglang.org/documentation/master/#popCount


I think the point of the example is that in zig you'd just do

  if (mask.alpha and mask.blue) {


In C I'm using a macro for stuff like that (improving readability, avoiding errors). In Zig I could use a `comptime` function, I presume?


It depends on what else you’re doing. In this situation normal function would suffice in either C or Zig.


What about?

    if (mask & WGPUColorWriteMask_Alpha & WGPUColorWriteMask_Blue) {
        // alpha and blue are set..
    }


> `mask & WGPUColorWriteMask_Alpha & WGPUColorWriteMask_Blue`

If WGPUColorWriteMask_Alpha and WGPUColorWriteMask_Blue doesn't share bits, isn't this garanteed 100% to be false?


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.


Yeah, you cant do that in Go, even explicitly:

    // cannot convert i (variable of type int) to type bool
    bool(i)
youd need to use a function:

    func to_bool(i int) bool { return i != 0 }


Seems a bit mean-spirited to provide casts but not that one.


not really. Go has no concept of truthy:

https://developer.mozilla.org/docs/Glossary/Truthy

and I fully support that decision. If you want to use a boolean, you need to be explicit about it.


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.


It's Go pretending it's a kind of language it isn't, and making the user do the teapot dance instead of cooperating.

People keep telling me this koolaid is delicious but I just don't see it.


It seems you ran out of actual arguments, sorry to hear that.


You can't argue with someone who doesn't understand logic, which is literally true here. I have run out of jokes at Go's expense.


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.

edit: oh, i think i'm wrong, nevermind.


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

Correct usage would be if you want both flags.

    (flag & (WGPUColorWriteMask_Alpha|WGPUColorWriteMask_Blue) == (WGPUColorWriteMask_Alpha|WGPUColorWriteMask_Blue)


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.


No, the expression will resolve to 'true' if any of the Alpha or Blue bits are set.


This is why bit sets are far better at this and should be used, i.e.:

    if WGPUColorWriteMask.Alpha in mask and WGPUColorWriteMask.Blue in mask: ...


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.


I believe parent was sarcastic and agrees with you.


I have to go to chandler’s sarcasm class. Can I BE any more obvious?


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.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: