Ruby language에서의 Symbol(심볼)이란?

언어 자체에 대한 글을 쓰는건 굉장히 오랜만인 것 같습니다. 오늘은 루비의 심볼에 대한 이야기를 하려합니다.

루비러라면 잘 아시겠지만, 루비는 모든것이 객체입니다. 그래서 일반적으로 객체라고 생각되지 않는 것들(문자열, 상수 등등) 또한 모두 객체입니다. (다른 언어도 비슷비슷하죠)

객체를 사용하기 위해서는 각 객체의 initialize를 통해

예를들어 루비 코드에 “GET” 이란 문자열이 5번 사용되었다고 칩니다. 이러면 이 프로그램이 실행되는 동안 “GET” 이란 문자열의 객체가 한번 생성되어 재 사용되는 것이 아닌, 각각 사용될 때 마다 String 객체를 생성하고 메모리를 잡아먹게 됩니다.

이래서 루비에서 적용된 개념은 심볼입니다.

Symbol?

심볼은 immutable, 즉 변경이 불가능한 객체입니다. 한번 생성되면 해당 객체 자체의 값을 변경할 수 없어지며, 동일한 이름으로 여러 부분에 사용될 수 있습니다.

심볼은 문자 앞에 콜론으로 표기하여 선언합니다. 예를들면.. A라는 심볼을 만들면

:A

가 되겠지요.

그럼 아까 위에 이야기를 이어서 해보죠. :GET이란 문자열 대신에 심볼로 만들게 되면 5번 사용될 동안 같은 심볼 객체를 참조하게 됩니다. 위에 문자열로 5번 사용하는 것 보다 공간을 절약하게 됩니다. 물론 5번 잡히나 1번 잡히나 크진 않습니다. 그렇지만, 사용되는 구간이 많아질수록 루비 인터프리터는 점점 많은 메모리를 할당하게 될거고, 결국은 성능에 문제를 끼칠 수 있습니다.

irb로 확인해보죠.

[RUBY-IRB] > "abcd" == "abcd"
=> true
[RUBY-IRB] > "abcd".object_id == "abcd".object_id
=> false

[RUBY-IRB] > :abcd == :abcd
=> true
[RUBY-IRB] > :abcd.object_id == :abcd.object_id
=> true

각 객체의 주소인 object_id 값으로 비교하면, 문자열의 경우 데이터(“abcd”)는 같지만, 실제 객체가 할당된 주소는 다릅니다. 반대로 심볼(:abcd)은 동일한 객체를 사용하게 되죠.

실행되고 있는 상태에서의 심볼은 Symbol 객체를 이용해서 확인이 가능합니다.

[RUBY-IRB] > Symbol.all_symbols.inspect
=> "[:!, :\"\\\"\", :\"#\", :\"$\", :%, :&, :\"'\", :\"(\", :\")\", :*, :+, :\",\", :-, :\".\", :/, :\":\", :\";\", :<, :\"=\", :>, :\"?\", :\"@\", :\"[\", :\"\\\\\", :\"]\", :^, :`, :\"{\", :|, :\"}\", :~, :\"..\", :\"...\", :+@, :-@, :**, :<=>, :<<, :>>, :<=, :>=, :==, :===, :!=, :=~, :!~, :[], :[]=, :\"::\", :\"&&\", :\"||\", :\"&.\", :max, :min, :freeze, :inspect, :intern, :object_id, :const_missing, :method_missing, :method_added, :singleton_method_added, :method_removed, :singleton_method_removed, :method_undefined, :singleton_method_undefined, :length, :size, :gets, :succ, :each, :proc, :lambda, :send, :__send__, :__attached__, :initialize, :initialize_copy, :initialize_clone, :initialize_dup, :to_int, :to_ary, :to_str, :to_sym, :to_hash, :to_proc, :to_io, :to_a, :to_s, :to_i, :bt, :bt_locations, :call, :mesg, :exception, :_, :\"\", :empty?, :eql?, :respond_to?, :respond_to_missing?, :\"<IFUNC>\", :\"<CFUNC>\", :\"core#set_method_alias\", :\"core#set_variable_alias\", :\"core#undef_method\", :\"core#define_method\", :\"core#define_singleton_method\", :\"core#set_postexe\", :\"core#hash_from_ary\", :\"core#hash_merge_ary\", :\"core#hash_merge_ptr\", :\"core#hash_merge_kwd\", :$_, :$~, :__autoload__, :__classpath__, :__tmp_classpath__, :__classid__, :to_f, :dig, :BasicObject, :Object, :Module, :Class, :equal?, :Kernel, :__refined_class__, :inherited, :included, :extended, :prepended, :nil?, :hash, :class, :singleton_class, :clone, :dup, :itself, :taint, :tainted?, :untaint, :untrust, :untrusted?, :trust, :frozen?, :methods, :singleton_methods, :protected_methods, :private_methods, :public_methods, :instance_variables, :instance_variable_get, :instance_variable_set, :instance_variable_defined?, :remove_instance_variable, :instance_of?, :kind_of?, :is_a?, :tap, :sprintf, :format, :Integer, :Float, :String, :Array, :Hash, :NilClass, :to_h, :new, :NIL, :included_modules, :include?, :name, :ancestors, :attr, :attr_reader, :attr_writer, :attr_accessor, :instance_methods, :public_instance_methods, :protected_instance_methods, :private_instance_methods, :constants, :const_get, :const_set, :const_defined?, :remove_const, :class_variables, :remove_class_variable, :class_variable_get, :class_variable_set, :class_variable_defined?, :public_constant, :private_constant, [...........]

Symbol을 String 처럼 사용하기

심볼은 한번 지정되면 변수에 문자열 데이터가 들어왔을 때 해당 변수의 문자열을 새로 만들지 않고 심볼을 참조하게 됩니다.

[RUBY-IRB] > :abcd.object_id
=> 1528988
[RUBY-IRB] > zzz = "abcd"
=> "abcd"

마치 심볼과 문자열이 다르게 선언되었을 것 같지만, intern으로 심볼 자체의 기호를 보면 :abcd를 가리키고 있습니다.

irb(main):080:0> zzz.intern
=> :abcd

Conclusion

결국은 어디서 사용하는가가 가장 중요한 부분일텐데, 구글링 해보면 많이 나오겠지만, 대체로 해시의 키값에 대한 이야기를 합니다. 계속 반복되는 String 생성이 있는 부분에선 String을 직접 만드는 것 보다 심볼로 정의해서 사용하는게 메모리나 성능적인 부분에서 이득이 됩니다.

추가로.. 심볼 가지고 놀던 중 알게되었는데, def로 선언하면 해당 객체 또한 심볼로 정의됩니다.

[RUBY-IRB] >   def addr
[RUBY-IRB] >   return self.object_id
[RUBY-IRB] >   end
=> :addr

재미있군요 :)

Reference

http://www.troubleshooters.com/codecorn/ruby/symbols.htm#_How_are_symbols_implemented https://ruby-doc.org/core-2.2.0/Symbol.html