Update: There’s a sequel to this post, called Son of 10 things…

I’ve been writing a lot about Ruby 1.9 (my book The Well-Grounded Rubyist is due out in a couple of months), and I thought I’d share my personal list of things you need to be careful of as you go from 1.8 to 1.9. This is not a list of changes; it’s a list of changes that you really need to know about to get your 1.8 code to work in 1.9, things that have a relatively high likelihood of biting you if you don’t know about them.

Strings are no longer enumerable

You can’t do string.each and friends any more. This has an impact, for example, on the Rack interface, where there has in the past been a requirement that the third item in the returned array respond to each.

Block argument semantics

This is a big change, and a big topic. The salient point is that when you do this:

  array.each {|x| ... }

the block parameter list is handled like a method parameter list. In 1.8, blocks use assignment semantics, so that @ is like @x=. That’s why in 1.8 you can do:

  array.each {|@x| ... }

(assign to an instance variable) or even:

  array.each {|self.attr| ... }

(call the attr= method on self). You can’t do those things in 1.9; the parameters are bound to the arguments using method-argument semantics, not assignment semantics.

Block variables scope

Block parameters are local to the block.

  x = 1
  [2,3].each {|x|  }

In 1.8, x would now be 3 (outside the block). In 1.9 the two x’s are not the same variable, so the original x is still 1.

However, a variable that (a) already exists, and (b) is not a block parameter, is not local to the block.

  x = 1
  [2,3].each {|y| x = y }
x is now 3. If you want or need to shield your existing variables from being used inside the block, declare variables as block local by putting them after a semi-colon in the parameter list:
  x = 1
  [2,3].each {|y;x| x = y }

x is still 1.

Method argument semantics

Method arguments do some new things too. In particular, you can now put required arguments after the optional argument glob parameter:

  def my_meth(a,*b,c)

There aren’t too many situations where you’d want to do this (though there are one or two).

The * operator has changed semantics

Compare 1.8:
  >> a = [1,2]
  => [1, 2]
  >> *b = a
  => [[1, 2]]
  >> b
  => [[1, 2]]

and 1.9:

  >> a = [1,2]
  => [1, 2]
  >> *b = a
  => [1, 2]
  >> b
  => [1, 2]

I’ve always interpreted the * operator in the following way:

The expression *x represents the contents of the array x, as a
list.

In 1.8, *b = [1,2] means that [1,2] is the contents of the array b, which means that b is [[1,2]]. The 1.9 semantics don’t seem to behave that way. I’m not sure what the new general rule for * is, or whether maybe I was wrong that there was such a rule that governed all cases (though I can’t think of an exception).

Hashes are ordered

This isn’t likely to bite you but it’s something to be aware of, both in your own code and in looking at the code of others. Hashes are ordered by insertion order. Reassigning to a key does not change the insertion placement of that key.

method and friends return symbols

Expressions like obj.methods and klass.instance_methods return symbols instead of strings in 1.9. That means that you might have to do to_s operations on them, if you need them as strings. However…

Symbols are string-like

... symbols have become very string-like. You can match them against regular expressions, run methods like #upcase and #swapcase on them, and ask them their size (i.e., their size in characters). I’m not sure what the purpose of this is. I’d just as soon have symbols not be any more string-like than they absolutely have to be.

Gems are automatically in the load path

When you start Ruby (or irb), your load path ($:) will include the necessary directories for all the gems on your system. That means you can just require things, without having to require rubygems first. You can manipulate the load path per gem version with the gem method.

Lots of enumerable methods return enumerators

Called without a block, most enumerable methods now return an enumerator. It’s fairly unusual to use the return value of blockless calls to map, select, and others, but it’s worth knowing that now you cannot assume that, for example, Array#each will always return its receiver.

You can use this feature to chain enumerators, though the circumstances in which chaining enumerators really buys you anything are pretty few. I don’t know of a case where you would do this:

  array.map.other_method { ... }

with the exception of map.with_index. The map call is essentially a pass-through filter here. (This was not true in early versions of 1.9, where you could attach knowledge of a block to a chained enumerator, but that behavior was removed.)

Incidentally, you win the prize (which is endless glory :-) if you can account for the difference between these two snippets:

  >> {1 => 2}.select {|x,y| x }
  => {1=>2}
  >> {1 => 2}.select.select {|x,y| x }
  => [[1, 2]]

It’s all about enumerators….

If you’re careful about these changes, and keep an eye out for others, you should be able to continue to have fun with Ruby in version 1.9 and beyond!

Sorry, comments are closed for this article.