Help us understand the problem. What is going on with this article?

Ruby 3.0で発生する「プロを目指す人のためのRuby入門」との差異について

はじめに

2020年12月25日に、Rubyの新しいバージョンであるRuby 3.0がリリースされました。
一方、2017年11月に出版した書籍「プロを目指す人のためのRuby入門」(通称・チェリー本。以下、本書)は執筆当時最新だったRuby 2.4.1を対象にしています。

本書は紙の本であるため、簡単に内容をアップデートすることができません。しかし、何もしないとどんどん内容が古くなってしまい、「本の通りやってみたけど、今使っているRubyとなんか動きが違う」ということになってしまいます。

そこで新しいRubyのバージョンがリリースされて、本書の説明と異なる部分が出てきたときは、毎回ネット上でその差異を説明するようにしています。その説明を読めば、動きが違う部分があってもきっと落ち着いて対処できるはず、という算段です。

というわけで、この記事ではRuby 3.0で発生する「プロを目指す人のためのRuby入門」との差異について説明します。

なお、本文に出てくる章番号や項番号は、書籍の中で使われている番号です。

参考: もう読みましたか?Ruby 2.5〜2.7の差異はこちら

Ruby 2.5から2.7にかけて発生する差異については以下の記事にまとめてあります。

上記の記事と重複する内容はこの記事では説明しません。
ですので、まだ読まれていない方は先に上の記事を読んでから、この記事に戻ってくることをお勧めします。

それでは以下が本編です。

本文の説明と実行結果が異なるもの

以下で説明する内容は、Ruby 3.0で実行した場合に本書(つまりRuby 2.4)で説明している内容と実行結果が異なる部分です。

変数やメソッド名に_1_2を使うと構文エラーが発生するようになった(2.2.8)

本書の2.2.8項では「変数名はアルファベットの小文字、またはアンダースコアで始め、それに続けてアルファベット、数字、アンダースコアで構成します」と説明しましたが、_1_2・・・_9はRuby 3.0では予約語となり、例外的に変数名やメソッド名として使用できなくなります。

# Ruby 3.0では_1や_2といった変数を宣言すると構文エラーになる
_1 = "999"
#=> SyntaxError (_1 is reserved for numbered parameter)

これはRuby 2.7でブロックの仮引数として番号指定パラメータ(numbered parameter)が導入された影響です。

%w(1 20 300).map { _1.rjust(3, '0') }
#=> ["001", "020", "300"]

_1_2のような変数はRuby 2.7では警告扱いになっていましたが、Ruby 3.0では構文エラーとなり、使用できなくなりました。

キーワード引数の代わりにハッシュを渡すとエラーが出るようになった(5.4.3)

Ruby 3.0ではキーワード引数の代わりにハッシュを渡すとがエラー(ArgumentError)が出るようになりました。
そのため、5.4.3項で説明した以下のコードは、Ruby 3.0で実行するとエラーが出ます。(Ruby 2.7で警告が出るようになった使い方です)

def buy_burger(menu, drink:, potato:)
  # 省略
end

params = { drink: true, potato: false }

# キーワード引数の代わりにハッシュを渡しているため、Ruby 3.0ではエラーになる
buy_burger('fish', params)
#=> ArgumentError (wrong number of arguments (given 2, expected 1; required keywords: drink, potato))

この場合は引数のハッシュオブジェクトに**を付けるとエラーが出なくなります。

# **を付けて呼び出すとエラーにならない
buy_burger('fish', **params)

Ruby 3.0におけるキーワード引数の仕様変更については、以下の記事にまとめているので、詳しく知りたい方はこちらをご覧ください。(基本的にRuby 2.7で警告が出ていた用法がRuby 3.0でエラーになります)

バックトレースの表示順が元に戻った(9.2.1、11章、本書全般)

Ruby 2.5から2.7まで、エラー発生時のバックトレースの表示が本書の記載と逆になっていました。
その内容はRuby 2.5リリース時に書いた以下の記事で説明しています。

Ruby 2.5で発生する「プロを目指す人のためのRuby入門」との差異について

Ruby 3.0では2.4時代と同じ表示順(上に行くほどエラーの発生場所に近い順番)に戻りました。よって、本書との差異がなくなったことになります。
以下はRuby 2.7とRuby 3.0のバックトレースの表示を比較した結果です。

エラーを起こすためのサンプルコード

class A
  def hoge
    fuga
  end

  def fuga
    1/0
  end
end
A.new.hoge

バックトレースの出力例(Ruby 2.5〜2.7)

# 下に行くほどエラーの発生場所に近い順番
$ ruby stacktrace_example.rb
Traceback (most recent call last):
  3: from stacktrace_example.rb:10:in `<main>'
  2: from stacktrace_example.rb:3:in `hoge'
  1: from stacktrace_example.rb:7:in `fuga'
stacktrace_example.rb:7:in `/': divided by 0 (ZeroDivisionError)

バックトレースの出力例(Ruby 3.0)

# 上に行くほどエラーの発生場所に近い順番(Ruby 2.4以前と同じ)
$ ruby stacktrace_example.rb
stacktrace_example.rb:7:in `/': divided by 0 (ZeroDivisionError)
  from stacktrace_example.rb:7:in `fuga'
  from stacktrace_example.rb:3:in `hoge'
  from stacktrace_example.rb:10:in `<main>'

ただし、rubyコマンドではなくirbを使って実行した場合はRuby 2.7と同じ表示結果になる点に注意してください。

# Rub 3.0のirbの実行結果(Ruby 2.7と同じ)
$ irb
irb(main):001:1* class A
irb(main):002:2*   def hoge
irb(main):003:2*     fuga
irb(main):004:1*   end
irb(main):005:1* 
irb(main):006:2*   def fuga
irb(main):007:2*     1/0
irb(main):008:1*   end
irb(main):009:0> end
=> :fuga
irb(main):010:0> A.new.hoge
Traceback (most recent call last):
        7: from /Users/jnito/.rbenv/versions/3.0.0-rc1/bin/irb:23:in `<main>'
        6: from /Users/jnito/.rbenv/versions/3.0.0-rc1/bin/irb:23:in `load'
        5: from /Users/jnito/.rbenv/versions/3.0.0-rc1/lib/ruby/gems/3.0.0/gems/irb-1.2.8/exe/irb:11:in `<top (required)>'
        4: from (irb):10:in `<main>'
        3: from (irb):3:in `hoge'
        2: from (irb):7:in `fuga'
        1: from (irb):7:in `/'
ZeroDivisionError (divided by 0)

このようにRuby 3.0ではrubyコマンドを使ったときとirbを使ったときとでバックトレースの表示が異なるため、本書を読み進めるときは適宜バックトレースの読み方を切り替える必要があります。

(2020.1.14追記)
irb 1.3.1でirbの表示順もRuby 2.4以前の表示順に戻りました。
ターミナルからgem install irbを実行し、最新のirbをインストールしてください。

KernelモジュールのopenメソッドでURLを開くとエラーが出る(第12章のコラム)

第12章に掲載した「requireの単位はライブラリ」というコラムのサンプルコードで、open-uriライブラリをrequireしたあとに、openメソッド(Kernelモジュールのopenメソッド)を使ってURLを開くコードがありますが、Ruby 3.0ではこのコードを実行するとエラーが出ます。(Ruby 2.7で警告が出るようになった使い方です)

require 'open-uri'

# openメソッド(Kernelモジュールのopenメソッド)を使うと、Ruby 3.0ではエラーが出る
open 'http://example.com'
#=> Errno::ENOENT (No such file or directory @ rb_sysopen - http://example.com)

上のコードはopenURI.openに変えると、エラーが出なくなります。

require 'open-uri'

# URI.openにすれば警告は出ない
URI.open 'http://example.com'

該当コラムの「たとえば、open-uriというライブラリをrequireすると」から後ろの部分は、Ruby 2.7およびRuby 3.0では以下のように読み替えてください。

たとえば、open-uriというライブラリをrequireすると、URIモジュールにopenメソッドが追加され(厳密にはprivateメソッドがpublicメソッドになり、さらに機能拡張される)、URLを開けるようになります(新しいクラスが使えるようになるわけではありません)。

# URIモジュールのopenメソッドは、もともとprivateメソッドなので呼び出せない
URI.open 'http://example.com'
#=> NoMethodError (private method `open' called for URI:Module)

# ただし、open-uriライブラリをrequireするとopenメソッドが使えるようになる
require 'open-uri'
URI.open 'http://example.com'
#=> #<StringIO:0x007fe8cc105d08 @base_uri=#<URI::HTTP http://...

open-uriライブラリの詳しい内容については、以下の公式リファレンスを参照してください。

Ruby 3.0で追加された新しい構文

このほかにもRuby 3.0では、本書執筆時点にはなかった新しい構文がいくつか追加されています。

1行パターンマッチングの構文がin=>の2種類になった(実験的機能)

Ruby 2.7ではinを使って1行パターンマッチングができました。

# Ruby 2.7
data = {name: 'Alice', age: 20, zodiac: 'Capricorn'}
data in {name:, zodiac:}
name   #=> "Alice"
zodiac #=> "Capricorn"

Ruby 3.0ではinだけでなく、=>も使えるようになりました。(右代入っぽく使える)

# Ruby 3.0では => も使える
data = {name: 'Alice', age: 20, zodiac: 'Capricorn'}
data => {name:, zodiac:}
name   #=> "Alice"
zodiac #=> "Capricorn"

また、inを使った1行パターンマッチングは、Ruby 3.0ではtrue/falseを返すようになりました。

data = {name: 'Alice', age: 20, zodiac: 'Capricorn'}
if data in {name:, zodiac:}
  # マッチした場合の処理(変数への代入もされる)
  name   #=> "Alice"
  zodiac #=> "Capricorn"
else
  # マッチしなかった場合の処理
end

ただし、1行パターンマッチングはどちらも実験的機能であるため、使用すると警告が出ます。

{a: 0, b: 1} => {b:}
#=> warning: One-line pattern matching is experimental, and the behavior may change in future versions of Ruby!

{a: 0, b: 1} in {b:}
#=> warning: One-line pattern matching is experimental, and the behavior may change in future versions of Ruby!

endlessメソッド定義構文が導入された(実験的機能)

Ruby 3.0はendを省略して1行でメソッドを定義できる、endlessメソッド定義構文が実験的に導入されました。

# endlessメソッド定義構文(実験的機能)
def version_3?(v) = v.to_f >= 3.0

version_3?('2.7') #=> false
version_3?('3.0') #=> true

上のメソッドは下のメソッド定義と同じ意味になります。

def version_3?(v)
  v.to_f >= 3.0
end

その他、注目の新機能など

特筆すべき差異は上で述べた内容になりますが、その他にもRuby 3.0では数多くの新機能や変更点が導入されています。
その中から注目度の高い新機能や変更点を以下にピックアップしておきます。

  • case/inを使うパターンマッチングが正式に導入された
  • 静的型解析の基盤が導入された(RBS、TypeProf)
  • 並行・並列処理プログラミングをサポートする新しいライブラリ、Ractorが追加された(実験的機能)
  • Ruby 2.7.2以降で非推奨警告はデフォルトで出力されなくなった(警告を出力する場合はRUBYOPT=-W:deprecatedを付けて実行する)

それぞれの詳細については(そしてそれ以外の新機能や変更点についても!)、下記の記事で詳しく説明しているのでこちらをご覧ください。

まとめ

というわけで、この記事ではRuby 3.0で発生する「プロを目指す人のためのRuby入門」との差異をまとめました。

Ruby 3.0はRuby 2.xからのメジャーバージョンアップとなりますが、基本的な構文や言語仕様は2.xと大きく変わっていません。
細かい点では後方互換性がなくなっている部分があったりするものの、Ruby初心者のうちは気にする必要がないものがほとんどです。
日常的なコーディングでは本書で説明している内容でじゅうぶん実務をカバーできているはずなので、安心して学習を進めてください。

Ruby初学者の方はこの記事で紹介した内容をいきなりキャッチアップしようとするよりも、まずは本書を最後まで読み進めましょう。
その上で余裕があればRuby 2.5、2.6、2.7、3.0と、順に変更点を吸収していくのがお勧めの学習方法になります。

PR:プロを目指す人のためのRuby入門について

「プロを目指す人のためのRuby入門」は、他の言語での開発経験があり、これからRubyを始めたい人や、Rubyプログラミングの経験はある程度あるものの、まだまだ自信がない人に向けて、Rubyの言語仕様や開発の現場で役立つ知識を詳しく、ていねいに解説したRubyの入門書です。
テスト駆動開発やデバッグ技法といった実践的なテクニックを学ぶこともできます。

まだ読まれていない方は、お近くの書店やAmazonで一度手に取っていただけると幸いです。
みなさん、どうぞよろしくお願いします😄

プロを目指す人のためのRuby入門|技術評論社
9784774193977.jpg

jnchito
SIer、社内SEを経て、ソニックガーデンに合流したプログラマ。 「プロを目指す人のためのRuby入門」の著者。 http://gihyo.jp/book/2017/978-4-7741-9397-7 および「Everyday Rails - RSpecによるRailsテスト入門」の翻訳者。 https://leanpub.com/everydayrailsrspec-jp
https://blog.jnito.com/
sonicgarden
「お客様に無駄遣いをさせない受託開発」と「習慣を変えるソフトウェアのサービス」に取り組んでいるソフトウェア企業
http://www.sonicgarden.jp
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away