In Ruby, you can write methods whose names end in ! (exclamation point or “bang”). There’s a lot of confusion surrounding the matter of when, and why, you would want to do so.

What ! does (and does not) mean

The ! in method names that end with ! means, “This method is dangerous”—or, more precisely, this method is the “dangerous” version of an otherwise equivalent method, with the same name minus the !. “Danger” is relative; the ! doesn’t mean anything at all unless the method name it’s in corresponds to a similar but bang-less method name.

So, for example, gsub! is the dangerous version of gsub. exit! is the dangerous version of exit. flatten! is the dangerous version of flatten. And so forth.

The ! does not mean “This method changes its receiver.” A lot of “dangerous” methods do change their receivers. But some don’t. I repeat: ! does not mean that the method changes its receiver.

Don’t overuse the !

Not every !-method changes its receiver, and not every receiver-changing method ends with !. There’s Array#pop/push/shift/unshift/concat/clear, and lots of others.

Don’t add ! to your destructive (receiver-changing) methods’ names, unless you consider the changing to be “dangerous” and you have a “non-dangerous” equivalent method without the !. If some arbitrary subset of destructive methods end with !, then the whole point of ! gets distorted and diluted, and ! ceases to convey any information whatsoever.

If you want to write a destructive method and you don’t think the name conveys destruction, you might be tempted to add a ! to make it clear. That’s not a good idea. If the name of a destructive method, without a !, does not connote destruction, then the name is wrong and cannot be repaired by slapping a ! on it.

In such a case, you should create the traditional pair of methods: a non-bang method and a bang method. It’s conventional to define the non-bang method in terms of the bang one. Here’s an example involving a simplistic version of an Array#flatten_once method (it doesn’t handle nested objects other than arrays very well, but it makes the bang-point):

  class Array
    def flatten_once!
      res = []
      each do |e|
        [*e].each {|f| res << f }
      end
      replace(res)
    end

    def flatten_once
      dup.flatten_once!
    end
  end
 

The non-bang method is defined in terms of the bang method. You wouldn’t want to write an isolated method called flatten_once! without the matching non-bang version, since there’s no way to measure the “danger” except in relation to the non-dangerous method.

[Aug. 16, comment in response to IRC question from apeiros: you can also define the bang method in terms of the non-bang method, but in the Ruby source code it’s usually done in the direction I’ve done it, and I’ve followed that practice here.]

Danger takes many forms

Sometimes you get more than one kind of “danger” even within one bang method. Take String#gsub!. This method changes its receiver:

  str = "David" 
  str.gsub!(/$/, " Black")
  str                        # David Black
  

It also differs from gsub (non-bang) in that if the string does not change, gsub returns a copy of the unchanged string but gsub! returns nil:

  str.gsub(/xyz/, "blah")    # David Black
  str.gsub!(/xyz/, "blah")   # nil
  str                        # David Black
  

The ! in gsub! gives you a heads-up: it warns you of danger, and that means that before you use the method, you should find out exactly how it behaves. (A simple “ri String#gsub!” should do it.)

Forget “intuitive”

It may not be easy or, to use a heavily overworked term, “intuitive” to think of ! as meaning “dangerous”, or to craft your method names so that the !’s make sense in that context. It’s worth it, though. Give Ruby (and Matz) the benefit of the doubt. This use of ! probably does not occur in other languages you’ve used. But it works really well. If you let go of the notion that ! means destructive, and if you think through the naming and pairing of dangerous and non-dangerous methods, you’ll see how valuable a flag the ! can be.

Sorry, comments are closed for this article.