Reputation: 678
I ran this code:
begin
print 'Enter something:'
x = gets.to_i # Enter a string
rescue => err
print(err.to_s)
end
I don't get why the rescue block does not catch the exception. It always returns zero when a string is input, and doesn't trigger the rescue block. I don't know why it isn't working. Can anyone please help?
Upvotes: 2
Views: 910
Reputation: 84453
The reason your exception handler is never called is because String#to_i doesn't raise an exception, even if it can't detect a valid integer within the String object. In such cases, it simply returns 0
.
In comparison, the behavior of Kernel#Integer is more complex, but is expected to raise ArgumentError or TypeError if the contents of the string do not strictly conform to a numeric representation.
So, to minimally refactor your existing code to raise an exception on non-numeric inputs:
begin
print 'Enter something: '
x = Integer gets
rescue => err
# Do something other than just print err on STDERR, which is the
# default behavior anyway. Perhaps send it to STDOUT instead.
puts "I received an exception: #{err}"
# After handling, re-raise the original exception with or without
# passing the original exception object. `raise` and `raise err`
# will do the same thing here.
raise
# For more advanced uses, you can also do something else like raise
# a different exception (e.g. TypeError), or modify the exception
# object stored in err and raise that modified object instead.
end
The following user inputs will each convert cleanly:
1
2
0xff
The code will even handle initial/trailing spaces, newlines, and carriage returns in most cases, without any additional effort on your part. However:
Enter something: one
ArgumentError: invalid value for Integer(): "one\n"
Enter something: "1"
ArgumentError: invalid value for Integer(): "\"1\"\n"
Enter something: nil
ArgumentError: invalid value for Integer(): "nil\n"
In general, you can rely on Kernel#Integer to raise an exception when necessary, which simplifies your code a lot. However, see caveats below.
These examples don't require it, but you might also want to sanitize your input with #strip, #chomp, or other string transformations when necessary. Your mileage in this regard will vary greatly with your real-world use case, but while Kernel#Integer generally does the right thing, and Ruby encourages relying on exceptions to handle non-standard edge cases, it's often unwise to trust user-tainted inputs.
It's also worth noting that both String#to_i and Kernel#Integer might operate on values other than user input, in which case know that Integer(nil)
will raise:
Integer nil
TypeError: can't convert nil into Integer
This might be important. Again, your mileage may vary.
Upvotes: 5