Code Fix

上級

可変長引数を委譲するメソッドで * を付け忘れると ArgumentError になる

受け取った可変長引数(*argsで受けた配列)をそのまま別のメソッドに渡すには、呼び出し側でも*を付けて展開する必要があります。*を付けずに渡すと、配列がまるごと1個の引数として渡ってしまい、引数の数が合わずArgumentErrorになります。

エラーメッセージの読み方

main.rb:1:in `total': wrong number of arguments (given 1, expected 3) (ArgumentError)
main.rb
ファイル名
1
行番号 — 実際にクラッシュした行
total
発生場所 — <main> ならトップレベル、メソッド名ならそのメソッドの中
ArgumentError
例外クラス — 何が起きたか。ここを検索するのが最短です
wrong number of arguments (given 1, expected 3)
詳細メッセージ — どの値が問題だったか

このエラーが出る典型パターン

パターン1

 1  def total(a, b, c)
 2    a + b + c
 3  end
 4  
 5  def wrapper(*args)
 6    total(args) 
              ^
 7  end
 8  
 9  puts wrapper(1, 2, 3)
main.rb:1:in `total': wrong number of arguments (given 1, expected 3) (ArgumentError) from main.rb:6:in `wrapper' from main.rb:9:in `<main>'

*args自体は配列というただ1つのオブジェクトです。展開せずに渡すと「配列1個」という引数として扱われます。

直し方: (args) を (*args) にします。

この問題を解いてみる →

広告
広告スロット(未設定)

パターン2

 1  def sum3(a, b, c)
 2    a + b + c
 3  end
 4  
 5  def wrapper(*args)
 6    sum3(args) 
             ^
 7  end
 8  
 9  puts wrapper(4, 5, 6)
main.rb:1:in `sum3': wrong number of arguments (given 1, expected 3) (ArgumentError) from main.rb:6:in `wrapper' from main.rb:9:in `<main>'

受け取った可変長引数を別のメソッドにそのまま横流ししたい場合は、呼び出し側でも*で展開する必要があります。

直し方: (args) を (*args) にします。

この問題を解いてみる →

パターン3

 1  def combine(a, b, c)
 2    "#{a}-#{b}-#{c}"
 3  end
 4  
 5  def wrapper(*args)
 6    combine(args) 
                ^
 7  end
 8  
 9  puts wrapper("x", "y", "z")
main.rb:1:in `combine': wrong number of arguments (given 1, expected 3) (ArgumentError) from main.rb:6:in `wrapper' from main.rb:9:in `<main>'

配列をそのまま渡せば元の個数のまま伝わるはず、という思い込みは誤りです。*の展開が無いと配列1個として渡ります。

直し方: (args) を (*args) にします。

この問題を解いてみる →

パターン4

 1  def merge_nums(a, b, c)
 2    a + b + c
 3  end
 4  
 5  def wrapper(*args)
 6    merge_nums(args) 
                   ^
 7  end
 8  
 9  puts wrapper(10, 20, 30)
main.rb:1:in `merge_nums': wrong number of arguments (given 1, expected 3) (ArgumentError) from main.rb:6:in `wrapper' from main.rb:9:in `<main>'

委譲先のメソッドが固定長の引数を要求している場合、*を忘れると必ず引数の数が合わずエラーになります。

直し方: (args) を (*args) にします。

この問題を解いてみる →

パターン5

 1  def build(a, b, c)
 2    [a, b, c]
 3  end
 4  
 5  def wrapper(*args)
 6    build(args) 
              ^
 7  end
 8  
 9  puts wrapper(7, 8, 9).inspect
main.rb:1:in `build': wrong number of arguments (given 1, expected 3) (ArgumentError) from main.rb:6:in `wrapper' from main.rb:9:in `<main>'

*argsで集めた引数を他のメソッドへ委譲するラッパーを書くときは、渡す側にも*を付ける必要があります。

直し方: (args) を (*args) にします。

この問題を解いてみる →

よくある誤解

「*argsで受け取った引数は、そのまま書けば元の個数のまま次のメソッドに渡るはず」という思い込みは誤りです。*argsは配列というただ1つのオブジェクトなので、展開せずに渡せば「配列1個」という引数として扱われます。

実務での勘所

Ruby 2.7以降は、...(3つのドット)という引数委譲の専用構文が使えます。def wrapper(...); target(...); endのように書けば、位置引数・キーワード引数・ブロックのすべてをまとめて、そのまま次のメソッドへ委譲できます。*argsや**kwargs、&blockを個別に受け取って個別に展開する必要が無くなるため、一部だけ展開し忘れるというミスが構造的に起こらなくなります。単純な委譲だけが目的のラッパーメソッドを書くときは、この構文が最も安全です。

演習をはじめる

関連するエラー

広告
広告スロット(未設定)