Many newcomers to Ruby are confused by symbols. A symbol is an identifier whose first character is a colon (:
), so :this
is a symbol and so is :that
. Symbols are, in fact, not at all complicated—and, in certain circumstances, they may be extremely useful, as you will see shortly.
Let’s first be clear about what a symbol is not: It is not a string, it is not a constant, and it is not a variable. A symbol is, quite simply, an identifier with no intrinsic meaning other than its own name. Whereas you might assign a value to a variable like this . . .
name = "Fred"
you would not assign a value to a symbol:
:name = "Fred" # Error!
The value of a symbol is itself. So, the value of a symbol called :name
is :name
.
For a more technical account of what a symbol is, refer to Digging Deeper in Digging Deeper.
You have, of course, used symbols before. In Chapter 2, for instance, you created attribute readers and writers by passing symbols to the attr_reader
and attr_writer
methods, like this:
attr_reader( :description ) attr_writer( :description )
You may recall that the previous code causes Ruby to create a @description
instance variable plus a pair of getter (reader) and setter (writer) methods called description
. Ruby takes the value of a symbol literally. The attr_reader
and attr_writer
methods create, from that name, variables and methods with matching names.
It is a common misconception that a symbol is a type of string. After all, isn’t the symbol :hello
pretty similar to the string "hello"
? In fact, symbols are quite unlike strings. For one thing, each string is different—so, as far as Ruby is concerned, "hello"
, "hello"
, and "hello"
are three separate objects with three separate object_id
s.
symbol_ids.rb
# These 3 strings have 3 different object_ids puts( "hello".object_id ) #=> 16589436 puts( "hello".object_id ) #=> 16589388 puts( "hello".object_id ) #=> 16589340
But a symbol is unique, so :hello
, :hello
, and :hello
all refer to the same object with the same object_id
.
# These 3 symbols have the same object_id puts( :hello.object_id ) #=> 208712 puts( :hello.object_id ) #=> 208712 puts( :hello.object_id ) #=> 208712
In this respect, a symbol has more in common with an integer than with a string. Each occurrence of a given integer value, you may recall, refers to the same object, so 10
, 10
, and 10
may be considered to be the same object, and they have the same object_id
. Remember that the actual IDs assigned to objects will change each time you run a program. The number itself is not significant. The important thing to note is that each separate object always has a unique ID, so when an ID is repeated, it indicates repeated references to the same object.
ints_and_symbols.rb
# These three symbols have the same object_id puts( :ten.object_id ) #=> 20712 puts( :ten.object_id ) #=> 20712 puts( :ten.object_id ) #=> 20712 # These three integers have the same object_id puts( 10.object_id ) #=> 21 puts( 10.object_id ) #=> 21 puts( 10.object_id ) #=> 21
You can also test for equality using the equal?
method:
symbols_strings.rb
puts( :helloworld.equal?( :helloworld ) ) #=> true puts( "helloworld".equal?( "helloworld" ) ) #=> false puts( 1.equal?( 1 ) ) #=> true
Being unique, a symbol provides an unambiguous identifier. You can pass symbols as arguments to methods, like this:
amethod( :deletefiles )
A method might contain code to test the value of the incoming argument:
symbols_1.rb
def amethod( doThis ) if (doThis == :deletefiles) then puts( 'Now deleting files...') elsif (doThis == :formatdisk) then puts( 'Now formatting disk...') else puts( "Sorry, command not understood." ) end end
Symbols can also be used in case
statements where they provide both the readability of strings and the uniqueness of integers:
case doThis when :deletefiles then puts( 'Now deleting files...') when :formatdisk then puts( 'Now formatting disk...') else puts( "Sorry, command not understood." ) end
The scope in which a symbol is declared does not affect its uniqueness. Consider the following:
symbol_ref.rb
module One class Fred end $f1 = :Fred end module Two Fred = 1 $f2 = :Fred end def Fred() end $f3 = :Fred
Here, the variables $f1
, $f2
, and $f3
are assigned the symbol :Fred
in three different scopes: module One
, module Two
, and the “main” scope. Variables starting with $
are global, so once created, they can be referenced anywhere. I’ll have more to say on modules in Chapter 12. For now, just think of them as “namespaces” that define different scopes. And yet each variable refers to the same symbol, :Fred
, and has the same object_id
.
# All three display the same id! puts( $f1.object_id ) #=> 208868 puts( $f2.object_id ) #=> 208868 puts( $f3.object_id ) #=> 208868
Even so, the “meaning” of the symbol changes according to its scope. In module One
, :Fred
refers to the class Fred
; in module Two
, it refers to the constant Fred = 1
; and in the main scope, it refers to the method Fred
.
A rewritten version of the previous program demonstrates this:
symbol_ref2.rb
module One class Fred end $f1 = :Fred def self.evalFred( aSymbol ) puts( eval( aSymbol.id2name ) ) end end module Two Fred = 1 $f2 = :Fred def self.evalFred( aSymbol ) puts( eval( aSymbol.id2name ) ) end end def Fred() puts( "hello from the Fred method" ) end $f3 = :Fred
First I access the evalFred
method inside the module named One
using two colons (::
), which is the Ruby “scope resolution operator.” I then pass $f1
to that method:
One::evalFred( $f1 )
In this context, Fred
is the name of a class defined inside module One
, so when the :Fred
symbol is evaluated, the module and class names are displayed:
One::Fred
Next I pass $f2
to the evalFred
method of module Two
:
Two::evalFred( $f2 )
In this context, Fred
is the name of a constant that is assigned the integer 1, so that is what is displayed: 1
. And finally, I call a special method called simply method
. This is a method of Object. It tries to find a method with the same name as the symbol passed to it as an argument and, if found, returns that method as an object that can then be called:
method($f3).call
The Fred
method exists in the main scope, and when called, its output is this string:
"hello from the Fred method"
Naturally, since the variables $f1
, $f2
, and $f3
reference the same symbol, it doesn’t matter which variable you use at any given point. Any variable to which a symbol is assigned, or, indeed, the symbol itself, will produce the same results. The following are equivalent:
One::evalFred( $f1 ) #=> One::Fred Two::evalFred( $f2 ) #=> 1 method($f3).call #=> hello from the Fred method One::evalFred( $f3 ) #=> One::Fred Two::evalFred( $f1 ) #=> 1 method($f2).call #=> hello from the Fred method One::evalFred( :Fred ) #=> One::Fred Two::evalFred( :Fred ) #=> 1 method(:Fred).call #=> hello from the Fred method
3.144.31.163