It’s common to see methods in Ruby with an exclamation mark or “bang” at the end:

# Hash#compact! removes all nil values

h = { a: 1, b: false, c: nil }
h.compact! #=> { a: 1, b: false }

While being one among a number of unique features that help make Ruby an enjoyable language, it is also possible to misuse or overuse this convention.

Bang methods are sometimes (mistakenly) used to represent any “destructive” method, but Ruby’s creator Matz describes them as follows:

The bang (!) does not mean “destructive” nor lack of it mean non destructive either. The bang sign means “the bang version is more dangerous than its non bang counterpart; handle with care”. Since Ruby has a lot of “destructive” methods, if bang signs follow your opinion, every Ruby program would be full of bangs, thus ugly. matz.

Continuing with the example of Hash#compact! mentioned above, here is its implementation, preceded by the non-bang version, which does not modify its receiver:

// hash.c

static VALUE
rb_hash_compact(VALUE hash)
{
    VALUE result = rb_hash_new();
    if (!RHASH_EMPTY_P(hash)) {
      rb_hash_foreach(hash, set_if_not_nil, result);
    }
    return result;
}

static VALUE
rb_hash_compact_bang(VALUE hash)
{
    st_index_t n;
    rb_hash_modify_check(hash);
    n = RHASH_SIZE(hash);
    if (n) {
      rb_hash_foreach(hash, delete_if_nil, hash);
      if (n != RHASH_SIZE(hash))
        return hash;
    }
    return Qnil;
}

[Poor C programmers, having to type out ‘b-a-n-g’!]

The non-bang version creates a new hash and passes that as the third argument to rb_hash_foreach, while rb_hash_compact_bang passes the hash argument directly to this function for modification.

Or with a pair of methods actually written in Ruby, taken from Rails:

# rails/activesupport/lib/active_support/hash_with_indifferent_access.rb

def transform_keys(*args, &block)
  return to_enum(:transform_keys) unless block_given?
  dup.tap { |hash| hash.transform_keys!(*args, &block) }
end

def transform_keys!
  return enum_for(:transform_keys!) { size } unless block_given?
  keys.each do |key|
    self[yield(key)] = delete(key)
  end
  self
end

The non-bang version of transform_keys creates a duplicate before performing modifications, while transform_keys! operates directly on itself.

Closing

As the language’s creator has described, any bang method should have a less dangerous counterpart. And it is probably a good idea to follow this guideline, to meet the expectations of other programmers.