I just learned about the existence &-=
operator in Crystal. What does it do?
Here is an example from Mutex#try_lock:
private def try_lock
i = 1000
while @state.swap(1) != 0
while @state.get != 0
Intrinsics.pause
i &-= 1
return false if i == 0
end
end
true
end
While trying out, I cannot see any difference to the familiar -=
operator. For example, these two snippets produce the same output:
i = 1000
while i != 0
puts i
i -= 1
end
i = 1000
while i != 0
puts i
i &-= 1
end
The first piece to the puzzle is to realize that a &-= b
is just syntax sugar for a = a &- b
. Or more generally a op= b
is syntax sugar for a = a op b
. The language reference details this under "Combined assignments" in Operators.
Now we need to ask what's &-
and how is it different from -
? Unfortunately the API docs are as of writing very quiet on this. The language reference is not very elaborate either, but on the same Operators page as above we can find:
-
subtraction
&-
wrapping subtraction
So what is wrapping subtraction? Well, Crystal has fixed size number types. So they may overflow, or underflow in this case. What does that mean? Let's have an example:
# We have something to sell! Let's keep track of how many!
# It doesn't really make sense to have negative something left,
# so an unsigned integer ought to this.
items_left = 2u32
# Just made the first sell! Let's remember
items_left -= 1
# People seem to actually like this
items_left -= 1
# I could do this all day!
items_left -= 1 # => Unhandled exception: Arithmetic overflow (OverflowError)
# Oh no what happend?
So the program tried to go below 0
, which the UInt32
type cannot represent. It underflowed. If Crystal wouldn't do this check, the CPU would happily wrap around on the integer type and we would get 4294967295
in items_left
(UInt32::MAX
).
But sometimes, in low level code, this behavior is what we want. For example if we're counting some statistic, say packets sent, if that counter over- or underflows, we don't want the program to fail in this case, wrapping around is ok. Or maybe we have some performance sensitive code and are sure it behaves correctly and will never overflow, so we don't want to pay the extra CPU cycles on checking whether the operation has just overflowed.
For these cases there's the &
prefixed math operators. They simply execute the operation without any overflow checking. Had we use &-
instead of -
in the example above, we would have 4294967295
in items_left
now.
In general you know in case you need or have a benefit from the wrapping operators. In doubt, just pretend they don't exist.