ruby gotchas and caveats
December 14th, 2006
While 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
1 2 3 4
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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
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 "oro?\n" => nil
- && and || are the high priority AND/OR constructs, and and or are low priority versions
That last one came as a surprise to me, as I expected it to return true. Guess not.1 2 3 4 5 6 7 8 9 10 11 12 13 14
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
- 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!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
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).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
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
1 2 3 4 5 6 7 8 9 10 11 12
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)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
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
December 19th, 2006 at 12:15 AM if 1 == true if 0 == false ...wouldn't have worked anyway. equality between incongruous types is *always* false
December 19th, 2006 at 09:47 AM In ruby that is true, but not in some other languages (C, C++ and php off the top of my head). So I think it's safe to say that this could be considered a gotcha if one was coming from another language.
December 19th, 2006 at 11:36 AM >> strings aren't auto converted into numbers or strings That is not a caveat, languages should behave this way. The caveat is in languages like Javascript and PHP.
December 19th, 2006 at 11:56 AM i use php daily at work. But at school I have used Ruby, Java, Ada95 and all of these will say "1" == 1 is false. This is true for most languages that dont coeherce types at runtime. Most of your points are well noted though.
December 19th, 2006 at 12:17 PM anon: Java, C# also auto-convert when faced with "A" + 1. Perl seems to do something funky but not throw an error/exception (perl monks pls correct me if I'm wrong). Python joins ruby in the exception throwing camp.
December 19th, 2006 at 11:13 PM Hi -- I'm not sure about #10. It seems to be more about class variables (which always seem to bring trouble) than singleton classes of class objects. Singleton classes of class objects actually tend to be modified more than any other singleton classes, since they are where class methods are stored.
December 19th, 2006 at 11:22 PM This post is great. Ruby's purposeful denial of encapsulation can be frustrating, and often leads to some too-clever-for-its-own-good code. This is a great round-up of tricky aspects. That being said, I disagree about #12. Scope is a fairly straight-forward subject, particularly when it's laid out in the fashion you describe. Unless you provide a better example, it's just not tricky at all. You might be pointing out that there's no interpreter complaint when referencing an unassigned variable, but that isn't a scope issue, just a gotcha all in itself (from a static language viewpoint). Thanks for the great post and keep churning them out!
December 20th, 2006 at 12:10 AM David Black makes a good point about 10. It's actually a class variable issue. Usually, class instance variables are more appropriate, anyway.
December 20th, 2006 at 12:20 AM David & Greg, my main question is why the difference in object point of view regarding singletons and class variables. I would think that the class variable references within the singleton would map over to the original class variables. Any pointers about this are appreciated, like David said, class variables seem to be a source of great confusion.
December 20th, 2006 at 05:51 AM segfault, unfortunately you didn't say what you find "funky" in Perl's behavior. For me, it just coerces: perl -wle'print "2" * 2' returning 4
December 20th, 2006 at 10:08 AM phaylon, I originally tested with "test: " + 2. Which just printed 2. I didn't test "2" + 1, which prints 3. This does make sense to me now.
December 20th, 2006 at 10:38 AM segy, I usually avoid class variables altogether. Is this more what you would expect? http://pastie.caboo.se/28770
December 20th, 2006 at 11:51 AM Greg, Yea, thats a lot closer to what I would expect. So when i want something that would be static in java/c#, I should be using an instance variable in a class/singleton context..?
December 20th, 2006 at 12:42 PM #7 is most interesting. I saw a plea, or demand, on some Rails blog to "stop using variables you don't own!". Basically they wanted to change core rails code or some such, but difficult when you've got alot of folks depending on version 1 let's say, and they, like any smart users, have made the most of the published interface. James Duncan Davidson gave a great talk on bulletproofing your interfaces (learned the hard way). Now in some languages, this is built-in, but it seems with Ruby it almost works against you, and you have to go out of your way to provide standard Object Oriented Design concepts like encapsulation. Please enlighten me on the philosophy here: OO stuff gets in the way sometimes? I'm all for scripting languages, dynamic stuff & the like, have lots of fun with awk & perl, but certainly issues like these seem to me to dictate that Ruby will have it's limitations in capturing more than a limited niche. Am I wrong on this? Provide references/examples if possible.
December 20th, 2006 at 02:22 PM Robert, it's worth noting that both Java and C# (all .net languages afaik) allow programmers to muck with private/protected data and methods using reflection. I'm of the opinion that this is useful, but as with any power tool serious care should be taken when using it. In an older post I wrote about some problems I encountered using feedtools that stemmed from mongrel monkey patching kernel. Anytime you monkey with the internals of someone else's code, your risking side-effects.
December 20th, 2006 at 10:56 PM After rereading #12, I completely take back what I said about scope not being tricky earlier. Good bit of information to know. I'm glad I haven't run across that little "feature" in any of my code.
December 21st, 2006 at 08:29 AM segfault, Yes, everything that doesn't look like a number for perl will be evaluated as 0 on coercion. Although if you activate warnings (which should be done always, and only skipped with good reasons) you'll get a "Argument "foo" isn't numeric in multiplication (*) at..." warning.
December 22nd, 2006 at 12:33 PM I just wanted to post about #11. Initially, that surprised me and seemed weird too. After some though, though, I realized it's actually a clever trick. It allows you to chain assignments. If object 'obj' has methods 'a=', 'b=', and 'c=', you can write code like this: obj.a = obj.b = obj.c = "hello" If the methods returned values as normal, then the programmer has to be sure to return the value assigned, rather than some arbitrary value, every time. With the choice Ruby made here, "hello" will be propagated to each assignment method, which is certainly not surprising.
December 23rd, 2006 at 08:19 PM Justin, offhand I would expect a call to obj.c after the call to obj.c=, to provide a value to obj.b=. It's nice that this functionality exists. When I think "what is the interpreter doing?", passing along the input value as the output is not the first thing that comes to mind. Might just be me though.