ruby gotchas and caveats
Thu, Dec 14, 2006 devWhile learning ruby I’ve run into a few gotchas that I think may be worth discussion/sharing. It’s worth noting that I’m coming from a Java,C#,C based background. These may not seem that odd to people coming from different backgrounds.
strings aren’t auto converted into numbers or strings
irb(main):001:0> "1" + 1 TypeError: can't convert Fixnum into String from (irb):1:in `+' from (irb):1
false and nil are the only valid false values, anything else is true
irb(main):001:0> p "rats" if 1 == true => nil irb(main):002:0> p "rats" if 0 == false => nil irb(main):003:0> p "yay?" if nil => nil irb(main):004:0> p "oro?" if 6 "oro?" => nil irb(main):005:0> p "oro?" if -6 "oro?\n" => nil irb(main):005:0> p "will print" if 0 "will print\n" => nil
&& and || are the high priority AND/OR constructs, and and or are low priority versions
irb(main):016:0> x = nil or 99 => 99 irb(main):017:0> x => nil irb(main):018:0> x = 42 and 99 => 99 irb(main):019:0> x => 42 irb(main):020:0> x = nil || 99 => 99 irb(main):021:0> x => 99 irb(main):022:0> x = 49 && 100 => 100
That last one came as a surprise to me, as I expected it to return true. Guess not.
Exceptions and raise vs catch.
catch/throw are not the same as raise/rescue. catch/throw allows you to quickly exit blocks back to a point where a catch is defined for a specific symbol, raise rescue is the real exception handling stuff involving the Exception object. If your exception doesn’t inherit from StandardError it will NOT be caught by default!
irb(main):001:0> class MyException < Exception
irb(main):002:1> end
=> nil
irb(main):003:0> def exception_miss
irb(main):004:1> raise MyException.new
irb(main):005:1> rescue
irb(main):006:1> p "saved!"
irb(main):007:1> end
=> nil
irb(main):008:0> begin
irb(main):009:1* exception_miss
irb(main):010:1> rescue Exception
irb(main):011:1> p "not so much"
irb(main):012:1> end
"not so much"
=> nil
irb(main):015:0> def exception_hit
irb(main):016:1> raise NewException
irb(main):017:1> rescue
irb(main):018:1> p "excellent!"
irb(main):019:1> end
=> nil
irb(main):020:0> exception_hit
"excellent!"
=> nil
irb(main):021:0> def catcher
irb(main):022:1> throw :testing
irb(main):023:1> rescue
irb(main):024:1> p "gotcha?"
irb(main):025:1> end
=> nil
irb(main):026:0> catcher
"gotcha?"
=> nil
irb(main):071:0> def throw_no_rescue
irb(main):072:1> throw :gotme
irb(main):073:1> end
=> nil
irb(main):074:0> def fire
irb(main):075:1> catch(:gotme) do
irb(main):076:2* p "start"
irb(main):077:2> throw_no_rescue
irb(main):078:2> p "after throw"
irb(main):079:2> end
irb(main):080:1> p "after catch"
irb(main):081:1> end
=> nil
irb(main):082:0> throw_no_rescue
NameError: uncaught throw 'gotme'
from (irb):72:in 'throw'
from (irb):72:in 'throw_no_rescue'
from (irb):82
from ♥:0
irb(main):083:0> fire
"start"
"after catch"
=> nil
- no class/module unloading
- no native unicode (ruby < 2.0)
private never truely private
irb(main):001:0> class MonkeyNinja irb(main):002:1> private irb(main):003:1> def secret_weakness irb(main):004:2> p "eep! dead monkey!" irb(main):005:2> end irb(main):006:1> end => nil irb(main):007:0> m = MonkeyNinja.new => #<MonkeyNinja:0x2c01d5c> irb(main):008:0> m.methods => [...no secret weakness! ] irb(main):009:0> m.private_methods => [stuff, "secret_weakness", "lambda"] irb(main):010:0> m.send :secret_weakness "eep! dead monkey!" => nil irb(main):011:0> m.secret_weakness NoMethodError: private method 'secret_weakness' called for #<MonkeyNinja:0x2c01d5c> from (irb):10 from ♥:0 irb(main):012:0> m.instance_eval do irb(main):013:1* secret_weakness irb(main):014:1> end "eep! dead monkey!" => nil irb(main):015:0> def m.kill_you irb(main):016:1> secret_weakness irb(main):017:1> end => nil irb(main):018:0> m.kill_you "eep! dead monkey!" => nil
1⁄2 != 0.5 unless require ‘mathn’ (this is the default in many languages anyway, sometimes integer math is your friend). With mathn 1⁄2 becomes a different type (Rational) than 0.5 (Float).
irb(main):010:0> 1.0/2
=> 0.5
irb(main):011:0> 1/2
=> 0
irb(main):012:0> p "normalcy" if 1/2 == 0.5
=> nil
irb(main):013:0> require 'mathn'
=> true
irb(main):014:0> 1/2
=> 1/2
irb(main):015:0> p "normalcy" if 1/2 == 0.5
"normalcy"
=> nil
irb(main):016:0> a = 0.5
=> 0.5
irb(main):017:0> a.class
=> Float
irb(main):018:0> b = 1/2
=> 1/2
irb(main):019:0> b.class
=> Rational
- class variables and their inheritance semantics
irb(main):001:0> class OmgFun
irb(main):002:1> @@taco = "testing"
irb(main):003:1> def taco
irb(main):004:2> @@taco = "me"
irb(main):005:2> end
irb(main):006:1> def self.to_s
irb(main):007:2> @@taco
irb(main):008:2> end
irb(main):009:1> end
=> nil
irb(main):010:0> class B < OmgFun
irb(main):011:1> @@taco = "B's taco"
irb(main):012:1> end
=> "B's taco"
irb(main):013:0> class C < B
irb(main):014:1> @@taco = "C's taco"
irb(main):015:1> end
=> "C's taco"
irb(main):016:0> C.to_s
=> "C's taco"
irb(main):017:0> B.to_s
=> "C's taco"
irb(main):018:0> OmgFun.to_s
=> "C's taco"
- singleton class modification for the class objects isn’t good or really allowed
irb(main):001:0> class Daffy
irb(main):002:1> @@mine = "all mine"
irb(main):003:1> def to_s
irb(main):004:2> @@mine
irb(main):005:2> end
irb(main):006:1> end
=> nil
irb(main):007:0> class << Daffy
irb(main):008:1> def jigga
irb(main):009:2> @@mine = "bunny time"
irb(main):010:2> end
irb(main):011:1> end
=> nil
irb(main):012:0> d = Daffy.new
=> all mine
irb(main):013:0> Daffy.jigga
(irb):9: warning: class variable access from toplevel singleton method
=> "bunny time"
irb(main):014:0> d
=> all mine
irb(main):015:0> d = Daffy.new
=> all mine
irb(main):016:0> def Daffy.wha
irb(main):017:1> @@mine = "wabbit"
irb(main):018:1> end
=> nil
irb(main):019:0> Daffy.wha
=> "wabbit"
irb(main):020:0> d
=> all mine
irb(main):021:0> d = Daffy.new
=> all mine
or
irb(main):001:0> class A
irb(main):002:1> @@foo = "bar"
irb(main):003:1> def self.print_foo
irb(main):004:2> p @@foo
irb(main):005:2> end
irb(main):006:1> end
=> nil
irb(main):007:0> A.print_foo
"bar"
=> nil
irb(main):008:0> A.class_eval do
irb(main):009:1* @@foo = "no go"
irb(main):010:1> end
=> "no go"
irb(main):011:0> A.print_foo
"bar"
=> nil
irb(main):012:0> A.class_eval do
irb(main):013:1* def self.new_hotness
irb(main):014:2> p @@foo
irb(main):015:2> end
irb(main):016:1> end
=> nil
irb(main):017:0> A.new
A.new A.new_hotness
irb(main):017:0> A.new
A.new A.new_hotness
irb(main):017:0> A.new_hotness
"no go"
=> nil
irb(main):018:0> A.instance_eval do
irb(main):019:1* p @@foo
irb(main):020:1> end
"no go"
=> nil
irb(main):021:0> A.module_eval do
irb(main):022:1* p @@foo
irb(main):023:1> end
"no go"
=> nil
irb(main):024:0> A.print_foo
"bar"
=> nil
- assignment methods always return the assigned value, not the return value of the assignment function
irb(main):001:0> class Foo
irb(main):002:1> def self.bar=(val)
irb(main):003:2> return "apeiros!"
irb(main):004:2> end
irb(main):005:1> end
=> nil
irb(main):006:0> Foo.bar = "meep"
=> "meep"
irb(main):007:0> Foo.bar
NoMethodError: undefined method 'bar' for Foo:Class
from (irb):7
from ♥:0
- local variable scoping is tricky and can hide other variables and methods (this is slated for a change in ruby 2.0)
irb(main):008:0> class Pizza
irb(main):009:1> def my_hut
irb(main):010:2> p "tasty!"
irb(main):011:2> end
irb(main):012:1> def breadstick
irb(main):013:2> if nil
irb(main):014:3> my_hut = "never executed"
irb(main):015:3> end
irb(main):016:2> my_hut
irb(main):017:2> end
irb(main):018:1> end
=> nil
irb(main):019:0> m = Pizza.new
=> #<Pizza:0x2c63cc8>
irb(main):020:0> m.breadstick
=> nil
- Another class variable trouble spot. You might think that class_eval would provide access to the class’s context and variables, apparently not so much. This example/issue came from Gary at a New Haven Ruby Brigade meeting.
irb(main):001:0> class A
irb(main):002:1> @@avar = "hello"
irb(main):003:1> end
=> "hello"
irb(main):004:0> A.class_variables
=> ["@@avar"]
irb(main):005:0> A.class_eval { puts @@avar }
NameError: uninitialized class variable @@avar in Object
from (irb):5
from (irb):5:in 'class_eval'
from (irb):5
from ♥:0
irb(main):006:0> A.instance_eval { puts @@avar }
NameError: uninitialized class variable @@avar in Object
from (irb):6
from (irb):6:in 'instance_eval'
from (irb):6
from :0
irb(main):007:0> A.module_eval { puts @@avar }
NameError: uninitialized class variable @@avar in Object
from (irb):7
from (irb):7:in 'module_eval'
from (irb):7
from ♥:0
irb(main):008:0> class A
irb(main):009:1> def my_avar
irb(main):010:2> @@avar
irb(main):011:2> end
irb(main):012:1> end
=> nil
irb(main):013:0> a = A.new
=> #<A:0x2c27890>
irb(main):014:0> a.my_avar
=> "hello"
irb(main):015:0> a.instance_eval { puts @@avar }
NameError: uninitialized class variable @@avar in Object
from (irb):15
from (irb):15:in 'instance_eval'
from (irb):15
from :0