在學 Ruby 時,常常會看到 :name 等,前面帶有冒號的字,那到底是什麼呢?


何謂 Symbol

Ruby 的官方 API 中這麼寫道:

Symbol 物件表示 Ruby 直譯器中的「名稱」及某些「字串」。
Symbol 可以用:name 和:“string” 語法以及各種 to_sym 方法生成。無論內容或含義為何,都會在程序執行的期間為該名稱或字串創建相同的 Symbol 物件。因此,假設 Fred 在一個 context 中是一個常數,在一個 context 中是方法,也是另一個 context 的類別, Symbol:Fred 全都是相同的物件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
module One
class Fred
end
$f1 = :Fred
end
module Two
Fred = 1
$f2 = :Fred
end
def Fred()
end
$f3 = :Fred
$f1.object_id # 2514190
$f2.object_id # 2514190
$f3.object_id # 2514190
  • Symbol 就是一個 Symbol class 的實體。
  • Symbol 不是變數,是「帶有名字的物件」,本質上就是「值」,不需要也不能指向任何東西。(變數則是指向某物件的名字,無法單獨存在。)
  • Symbol 即使被重複使用,也都是同一個物件,object_id 不會變,不會消耗多餘的記憶體。

Symbol 的取得方式

1
2
3
4
:symbol
:'symbol'
%s!symbol! # %記法
# 可使用 Symbol.all_symbols 取得所有已生成的 Symbol 列表。
  • 當宣告新的變數 / 方法 / 類別時,Ruby 會偷偷新增新的 Symbol
1
2
im_chai = "Chai"
Symbol.all_symbols.map(&:to_s).include?("im_chai") # true

Symbol 的命名規則

Symbol 的表現方法為「:」後跟一個標識符,一個方法名稱(包括如「!」「?」「=」等等的後綴詞),一個變數名(包括如「$」的前綴詞等),英文或非英文皆可。唯一例外的是, Symbol 不接受「\」字元。

1
2
3
4
:LOCK_SH
:$<
:こんにちは
:”Good Day”



Symbol 與 String 的比較

  • 相同 Symbol 指向的都是同一個記憶體,相同的 String 在每次產生時,都是在不同記憶體位置的不同物件。
1
2
3
4
5
6
7
8
9
10
11
12
13
3.times do
puts "hello".object_id
end

# 70199659402580
# 70199659366640
# 70199659366560
5.times do
puts :hello.object_id
end
# 899228
# 899228
# 899228

  • Symbol 效能較好 / String 較差

    做比較的時候,Symbol 是比對 object id 是否相同,而 String 則是一個字母一個字母逐一比對。所以在效能上來說,String 在做比較的時間複雜度會隨著字母的數量而增加,但 Symbol 的比較因為只比較是不是同一顆物件,所以它的複雜度是固定的。

1
2
3
4
p "abc" == "abc"           # true
p "abc".equal?("abc") # false
p :abc == :abc # true
p :abc.equal?(:abc) # true

  • Symbol 可使用的方法較少 / String 較多(Symbol class 的某些方法與 String class 的方法具有相同的名稱和功能)
1
2
3
4
5
6
:hello.length                  # 5
:hello.upcase # :HELLO
:hello[0] # "h"
:"foobar--".capitalize # "Foobar--"
:foo.encoding # #<Encoding:US-ASCII>
:哈哈.encoding # #<Encoding:UTF-8>

  • Symbol 內容無法更動 / String 可以
1
2
3
4
5
6
p :symbol           # :symbol
p "symbol" # "symbol"
p :symbol[2] # m
p "symbol"[2] # m
:symbol[2] = "k" # error
"symbol"[2] = "k"

  • 如果把 String 給”冰凍”(freeze)起來,它便不可修改,object id 也會是同樣的。
1
2
3
4
5
6
3.times do
puts "hello".freeze.object_id
end
# => 70314415546380
# => 70314415546380
# => 70314415546380

Symbol 與 String 的轉換

  • String 轉 Symbol:
1
2
p "foo".intern                   # :foo
p "foo".intern.to_s == "foo" # true
  • Symbol 轉 String:
1
2
p :foo.id2name                   # "foo"
p :foo.id2name.intern == :foo # true



Symbol 的使用時機

由於 Symbol 不可變更(immutable),以及效能較好,當內容不會變動,又不需要使用 String 的那麼多方法時,即可使用 Symbol。

例如:

  • Hash 中的 Key
1
profile = { name: "Chai", age: 18 }   # {:name=>"Chai", :age=>18}

  • Rails 的方法裡
1
2
3
4
5
6
7
8
9
10
11
def show
@page = Page.find(params[:id])
end
def create
@page = Page.create(page_params)
if page.save
redirect_to pages_path
else
ender :new
end
end

  • Proc
1
%w(A B C).map(&:to_sym)      # [:A, :B, :C]

多數時候 String 與 Symbol 可以互換使用,但也有只能使用 Symbol 或 String 的狀況,使用前務必詳閱 API 手冊。



參考資料:
https://ruby-doc.org/core-2.5.0/Symbol.html
https://docs.ruby-lang.org/ja/latest/class/Symbol.html
https://kaochenlong.com/2016/04/25/string-and-symbol/
https://www.ibm.com/developerworks/cn/opensource/os-cn-rubysbl/index.html
https://pjchender.github.io/2017/09/26/ruby-symbol%EF%BC%88%E7%AC%A6%E8%99%9F%EF%BC%89/
https://ithelp.ithome.com.tw/articles/10161202