読者です 読者をやめる 読者になる 読者になる

Crystal は String#to_i がエラーになることがあってつらい

これは「Ruby脳にはCrystalつらい Advent Calendar 2015」の15日目の記事です。

qiita.com

Ruby の String#to_i は数字以外の文字列の場合に 0 を返します。C の atoi() と同じです。

% ruby -e 'p "abc".to_i'   
0

Crystal の String#to_i は数字以外の文字列の場合にエラーになります。

% crystal eval 'p "abc".to_i'
invalid Int32: abc (ArgumentError)
[4367687] *CallStack::unwind:Array(Pointer(Void)) +87
[4367578] *CallStack#initialize<CallStack>:Array(Pointer(Void)) +10
[4367530] *CallStack::new:CallStack +42
[4367423] *Exception +31
[4367357] *ArgumentError#initialize<ArgumentError, String>:CallStack +29
[4367297] *ArgumentError::new<String>:ArgumentError +97
[4365729] *String#to_i32<String, Int32, Bool, Bool, Bool, Bool>:Int32 +257
[4365456] *String#to_i<String, Int32, Bool, Bool, Bool, Bool>:Int32 +32
[4365404] *String#to_i<String>:Int32 +92
[4347097] ???
[4354112] main +32
[140343524641344] __libc_start_main +240
[4323881] _start +41
[0] ???

Ruby の String#to_i よりは Integer() に似ています。

% ruby -e 'p Integer("abc")'
-e:1:in `Integer': invalid value for Integer(): "abc" (ArgumentError)
    from -e:1:in `<main>'

Crystal の気持ちはわかるような気がしますが、ちょっとつらい。

String#to_i? というメソッドもありますが、これは 0 ではなく nil を返します。

% crystal eval 'p "abc".to_i?'  
nil

Ruby のようにエラーにせずに 0 を返すには、0 を返すブロックを指定します。

% crystal eval 'p "abc".to_i{0}' 
0

Ruby の String#to_i は数字で始まっていれば、後ろに数字以外の文字があってもエラーにならず、数字部分の数値を返します。

% ruby -e 'p "123abc".to_i'
123

これも Crystal ではエラーになってしまいますが、引数に strict: false を渡すとと数字部分だけを評価します。

% crystal eval 'p "123abc".to_i(strict: false)' 
123

Rubyと同じように、先頭が数字以外の場合に 0 を返して、かつ後続する数字以外の文字を無視するようにするには、to_i(strict: false){0} とすればよいです。

% crystal eval 'p "abc".to_i(strict: false){0}' 
0
% crystal eval 'p "123abc".to_i(strict: false){0}' 
123