Computer >> 컴퓨터 >  >> 프로그래밍 >> Ruby

Ruby 심볼(Symbol) 완벽 가이드: 개념부터 Symbol GC까지

Ruby의 심볼(symbol)은 다음과 같은 형태로 생겼습니다:

:testing

일부 사람들은 심볼을 변수와 혼동하기도 하지만, 사실 심볼은 변수와는 아무런 관련이 없습니다…

심볼은 오히려 문자열과 훨씬 더 유사한 존재입니다.

그렇다면 Ruby에서 심볼과 문자열은 어떤 차이가 있을까요?

문자열은 데이터를 다룰 때 사용됩니다.

반면 심볼은 식별자(identifier)입니다.

이것이 바로 핵심적인 차이입니다:

심볼은 단순히 또 다른 종류의 문자열이 아니라, 서로 다른 목적을 가진 별개의 존재입니다.

심볼을 사용해야 하는 경우

심볼의 가장 흔한 용도 중 하나는 메서드명과 인스턴스 변수의 이름을 표현하는 것입니다.

예제:

attr_reader :title

attr_reader 뒤에 오는 :title@title 인스턴스 변수를 나타내는 심볼입니다.

또한 심볼을 해시(Hash)의 키로 사용할 수도 있습니다.

예제:

hash = {a: 1, b: 2, c: 3}

그렇다면 어떤 이점이 있을까요?

심볼은 시각적으로 더 깔끔하고, 불변(immutable)이며, 실제로 문자열 키와 심볼 키를 벤치마크로 비교해 보면 문자열 키가 약 1.7배 정도 느리다는 결과가 나옵니다.

여기서 불변(immutable)이라는 의미는 모든 심볼이 고유하며 변경할 수 없다는 뜻입니다:

:rubyguides.object_id
# 437768

:rubyguides.object_id
# 437768

이 두 심볼은 객체 ID(object id)가 동일하기 때문에 같은 객체입니다.

심볼은 send 같은 메타프로그래밍 메서드에서도 활용할 수 있습니다:

[1,2,3].send(:first)

정리하면:

메서드처럼 무언가의 이름 또는 라벨을 지정할 때는 심볼을 사용하고, 데이터(개별 문자) 자체가 중요할 때는 문자열을 사용하세요.

문자열과 심볼 간 변환하기

필요하다면 심볼을 문자열로 변환할 수 있습니다.

왜 그럴 필요가 있을까요?

심볼에는 String 클래스가 제공하는 메서드 중 일부만 존재하기 때문입니다.

또한 심볼은 변경할 수 없으므로, 심볼의 개별 문자를 다루고 싶다면 문자열로 변환해야 합니다.

이때는 to_s 메서드를 사용하면 됩니다.

예를 들어, method_missing을 사용하면 존재하지 않는 메서드의 이름이 심볼 형태로 전달됩니다. 이 이름이 특정 패턴(예: 물음표 ?로 끝나는지)과 일치하는지 검사하고 싶을 수 있습니다.

예제:

def method_missing(method_name, *args, &block)
  if method_name.to_s[-1] == "?"
    # do something
  else
    super
  end
end

반대로 문자열 객체를 심볼 객체로 변환할 수도 있습니다.

이때 사용하는 메서드는 String#to_sym입니다:

"rubyguides".to_sym

:rubyguides

심볼 배열 만들기

심볼 배열을 만들고 싶다면 다음 코드를 사용할 수 있습니다:

symbols = %i(a b c)

[:a, :b, :c]

콜론(:)과 콤마(,)를 일일이 입력하지 않아도 되어 매우 편리합니다.

문자열 버전인 %w와 비슷한 문법입니다:

strings = %w(a b c)

["a", "b", "c"]

심볼 GC (고급)

심볼에 대해 알아두면 좋은 또 다른 사실은 심볼에도 여러 종류가 있다는 점입니다.

그 이유는 Ruby 2.2 이전에는 심볼이 가비지 컬렉션(GC) 대상이 아니었기 때문입니다. 즉, 문자열, 해시, 배열 같은 일반 Ruby 객체와 달리 더 이상 필요하지 않아도 메모리에서 정리되지 않았습니다.

직접 예제를 실행해 보세요:

p Symbol.all_symbols.size
# 2443

('aa'..'aj').map(&:to_sym)

GC.start
p Symbol.all_symbols.size
# 2453

새로운 심볼 10개를 생성했기 때문에 전체 심볼 수가 정확히 10개 증가한 것을 확인할 수 있습니다.

하지만 Ruby 2.2부터는 이러한 심볼들이 임시로 생성되어 코드의 다른 부분에서 참조되지 않으므로 메모리에서 제거됩니다.

Symbol GC가 활성화된 Ruby 버전에서 이 코드를 실행하면 두 카운트가 동일하게 나타날 것입니다.

일부 심볼은 메모리에서 절대 제거되지 않는데, 이를 "불멸 심볼(immortal symbol)"이라고 부릅니다.

ObjectSpace 모듈을 사용하면 이들을 세어 볼 수 있습니다:

require 'objspace'

ObjectSpace.count_symbols

{
  :mortal_dynamic_symbol=>3,
  :immortal_dynamic_symbol=>5,
  :immortal_static_symbol=>3663,
  :immortal_symbol=>3668
}

:a1처럼 리터럴로 직접 생성한 심볼은 자동으로 불멸 심볼이 된다는 점에 주목하세요. 새로운 메서드를 정의하면 그에 대응하는 immortal_static_symbol도 함께 생성됩니다.

그렇다면 소멸 가능한(mortal) 심볼은 어디에서 오는 걸까요?

to_sym 메서드를 통해 문자열을 심볼로 변환할 때 생성됩니다.

ObjectSpace.count_symbols를 사용하여 직접 확인해 볼 수 있습니다.

immortal_dynamic_symbol이 궁금하신 분들을 위해 설명하자면, 이는 mortal 상태에서 immortal 상태로 승격된 심볼을 의미합니다. mortal 심볼의 이름으로 메서드를 정의할 때 이러한 승격이 발생할 수 있습니다.

마무리

이번 글에서 배운 내용을 정리하면 다음과 같습니다:

  • 심볼은 불변(immutable)이다
  • 심볼은 값을 가리키는 포인터가 아니라 값 그 자체이다
  • 문자열은 데이터를 위한 것이고, 심볼은 식별자(identity)를 위한 것이다
  • 문자열과 심볼 간의 변환 방법
  • Ruby 2.2에서 임시 심볼을 정리하기 위해 Symbol GC가 도입되었다

유익한 내용이었다면 주변에 공유해 주세요. 더 많은 사람들이 도움을 받을 수 있습니다 🙂