Float(nil) refuses what nil.to_f forgives
Question
What's the return value of the following Ruby 3 code?
nil.to_a # => []
Array(nil) # => []
nil.to_h # => {}
Hash(nil) # => {}
nil.to_s # => ""
String(nil) # => ""
nil.to_f # => 0.0
Float(nil) # => ???
The correct answer is
-
0.0 -
nil - It raises an error Correct
-
0
Explanation
TL;DR
Float(nil) raises TypeError: can't convert nil into Float. The Kernel conversion functions split into two families: the collection and string converters (Array(), Hash(), String()) map nil to an empty value, while the numeric ones (Float(), Integer()) are strict validators that refuse nil instead of inventing a 0.
Step by step
The forgiving converters delegate to conversion methods and treat nil as "absence of value":
Array(nil) # => []
Hash(nil) # => {}
Hash([]) # => {}
String(nil) # => ""
Array(arg)triesto_ary, thento_a.nil.to_ais[]. An object with neither method gets wrapped:Array(1) # => [1].Hash(arg)triesto_hash, special-casingniland[]to{}. Anything else withoutto_hashraisesTypeError.String(arg)triesto_str, thento_s.nil.to_sis"".
The numeric constructors do not fall back to to_f/to_i semantics for nil:
nil.to_f # => 0.0
begin
Float(nil)
rescue TypeError => e
e.message # => "can't convert nil into Float"
end
begin
Integer(nil)
rescue TypeError => e
e.message # => "can't convert nil into Integer"
end
The rationale: nil.to_f answers "give me your best float", and 0.0 is a guess. Float() answers "this must represent a float", and nil does not represent any number, so guessing 0.0 would silently corrupt data (think of a missing price becoming free).
Edge cases
The strictness extends to strings. Float() parses the full string or raises, where to_f happily returns garbage-tolerant defaults:
"123.45".to_f # => 123.45
"abc".to_f # => 0.0
Float("123.45") # => 123.45
begin
Float("abc")
rescue ArgumentError => e
e.message # => "invalid value for Float(): \"abc\""
end
Note the two error classes: a nil argument raises TypeError (wrong type), an unparseable string raises ArgumentError (right type, wrong content).
Since Ruby 2.6, exception: false turns both failures into nil, which makes Float() a clean validation tool:
Float(nil, exception: false) # => nil
Float("abc", exception: false) # => nil
Float("1.5", exception: false) # => 1.5
Use to_f/to_i when a default of zero is genuinely fine, and Float()/Integer() when the input is untrusted and a bad value should fail loudly (or return nil with exception: false).
Share this quiz
Comments
No comments yet. Be the first.