Search code examples
crystal-lang

What is the "&-=" operator in Crystal Lang?


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

Solution

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