코드 주석

코드 주석 (Code Comments)

Ruby에는 주석이 두 종류가 있어요. 한 줄 주석(inline)과 블록 주석(block)이죠. 그리고 일반 주석과 달리 코드 해석에 영향을 주는 특별한 '매직 주석'도 함께 알아둘게요.

출처: Ruby 공식 문서

본문

한 줄 주석 (Inline comments)

한 줄 주석은 # 문자로 시작해서 그 줄의 끝까지 계속돼요.

# On a separate line
class Foo # or at the end of the line
  # can be indented
  def bar
  end
end

블록 주석 (Block comments)

블록 주석은 =begin으로 시작해서 =end로 끝나요. 둘 다 각자 독립된 줄에서 시작해야 해요.

=begin
This is
commented out
=end

class Foo
end

=begin some_tag
this works, too
=end

=begin=end는 들여쓸 수 없어요. 그래서 다음 코드는 문법 오류예요.

class Foo
  =begin
  Will not work
  =end
end

매직 주석 (Magic Comments)

주석은 보통 Ruby가 무시하는데, 특별한 '매직 주석'은 코드가 어떻게 해석될지에 영향을 주는 지시문(directive)을 담아요.

최상위(top-level) 매직 주석은 파일의 첫 번째 주석 구간에 나타나야 해요.

NOTE: 매직 주석은 그것이 나타난 파일에만 영향을 줘요. 다른 파일에는 영향이 없어요.

# frozen_string_literal: true

var = 'hello'
var.frozen? # => true

대체 문법 (Alternative syntax)

매직 주석은 위 예시처럼 지시문 하나만 담을 수도 있어요. 또는 여러 지시문을 ;로 구분하고 -*-로 감싸서 같은 줄에 나란히 쓸 수도 있어요(Emacs의 파일 변수 방식이에요).

# emacs-compatible; -*- coding: big5; mode: ruby; frozen_string_literal: true -*-

p 'hello'.frozen? # => true
p 'hello'.encoding # => #<Encoding:Big5>

encoding 지시문 (Directive)

문자열 리터럴, 정규식 리터럴, __ENCODING__이 사용할 문자열 인코딩을 지정해요.

# encoding: big5

''.encoding # => #<Encoding:Big5>

기본 인코딩은 UTF-8이에요.

최상위 매직 주석은 첫 번째 줄에 시작해야 해요. 단, 첫 번째 줄이 #! 셔뱅(shebang) 줄처럼 보이면 두 번째 줄에 시작해도 돼요.

encoding 대신 coding이라는 단어를 써도 돼요.

frozen_string_literal 지시문 (Directive)

문자열 리터럴이 파싱 시점에 한 번만 할당되고 동결(frozen)되도록 해요.

# frozen_string_literal: true

3.times do
  p 'hello'.object_id # => prints same number
end
p 'world'.frozen? # => true

기본값은 false예요. 이 값은 --enable=frozen-string-literal로 바꿀 수 있어요. 지시문이 없거나 # frozen_string_literal: false로 되어 있으면 위 예시는 서로 다른 숫자 3개와 false를 출력할 거예요.

Ruby 3.0부터는 동적인(dynamic) 문자열 리터럴은 동결되지도 않고 재사용되지도 않아요.

# frozen_string_literal: true

p "Addition: #{2 + 2}".frozen? # => false

이 지시문은 파일의 첫 번째 주석 구간에 나타나야 해요.

warn_indent 지시문 (Directive)

이 지시문은 그 뒤에 오는 문장들에 대해 잘못된 들여쓰기 감지를 켤 수 있어요.

def foo
  end # => no warning

# warn_indent: true
def bar
  end # => warning: mismatched indentations at 'end' with 'def' at 6

이런 경고를 보는 또 다른 방법은 경고와 함께 ruby를 실행하는 거예요(ruby -w). 지시문으로 false로 설정하면 이 경고들이 나타나지 않도록 막을 수 있어요.

shareable_constant_value 지시문 (Directive)

Note: 이 지시문은 Ruby 3.0에서 실험적(experimental)이며 향후 릴리스에서 바뀔 수 있어요.

이 특별한 지시문은 불변(immutable) 객체만 담거나 Ractor-공유 가능한 상수를 만드는 데 도움을 줘요.

이 지시문은 상수에 할당되는 값에 대한 특별한 처리를 지정할 수 있어요.

  • none: (기본값)
  • literal: 리터럴은 암묵적으로 동결되고, 그 외의 값은 Ractor-공유 가능해야 함
  • experimental_everything: 모든 값을 공유 가능하게 만듦
  • experimental_copy: 깊게 복사하고 공유 가능하게 만듦
none 모드 (기본값)

이 모드에서는 특별한 처리가 없어요(Ruby 2.x와 동일). 자동 동결도, 검사도 없죠.

상수를 깊게 동결하는 것은 항상 좋은 생각이었는데, Ractor가 등장하면서 더 좋아졌어요. 공유 불가능한 상수는 메인 래터(main ractor)만 접근할 수 있으니까요.

# shareable_constant_value: none
A = {foo: []}
A.frozen? # => false
Ractor.new { puts A } # => can not access non-shareable objects by non-main Ractor.
literal 모드

'literal' 모드에서 리터럴에 할당되는 상수는 깊게 동결돼요.

# shareable_constant_value: literal
X = [{foo: []}] # => same as [{foo: [].freeze}.freeze].freeze

그 외의 값은 공유 가능해야 해요.

# shareable_constant_value: literal
X = Object.new # => cannot assign unshareable object to X

상수에 직접 할당된 리터럴이나, 그런 리터럴에 재귀적으로 담긴 값만 동결된다는 점을 기억하세요.

# shareable_constant_value: literal
var = [{foo: []}]
var.frozen? # => false (assignment was made to local variable)
X = var # => cannot assign unshareable object to X

X = Set[1, 2, {foo: []}].freeze # => cannot assign unshareable object to X
                                # (`Set[...]` is not a literal and
                                # `{foo: []}` is an argument to `Set.[]`)

Module#const_set 메서드는 영향을 받지 않아요.

experimental_everything 모드

이 모드에서는 상수에 할당되는 모든 값이 공유 가능하게 만들어져요.

# shareable_constant_value: experimental_everything
FOO = Set[1, 2, {foo: []}]
# same as FOO = Ractor.make_sharable(...)
# OR same as `FOO = Set[1, 2, {foo: [].freeze}.freeze].freeze`

var = [{foo: []}]
var.frozen? # => false (assignment was made to local variable)
X = var # => calls `Ractor.make_shareable(var)`
var.frozen? # => true

이 모드가 '실험적'인 이유는 실수하기 쉽기 때문이에요. 예를 들어 외부 리소스의 상수를 깊게 동결해서 오류를 일으킬 수 있어요.

# shareable_constant_value: experimental_everything
FOO = SomeGem::Something::FOO
# => deep freezes the gem's constant!

이 모드는 Ruby 3.1 전에 재검토되어 everything을 허용하거나 이 모드를 제거할 예정이에요.

Module#const_set 메서드는 영향을 받지 않아요.

experimental_copy 모드

이 모드에서는 상수에 할당되는 모든 값이 깊게 복사되고 공유 가능하게 만들어져요. experimental_everything보다 안전한 모드예요.

# shareable_constant_value: experimental_copy
var = [{foo: []}]
var.frozen? # => false (assignment was made to local variable)
X = var # => calls `Ractor.make_shareable(var, copy: true)`
var.frozen? # => false
Ractor.shareable?(X) #=> true
var.object_id == X.object_id #=> false

이 모드는 '실험적'이고 충분히 논의되지 않았어요. 이 모드는 Ruby 3.1 전에 재검토되어 copy를 허용하거나 이 모드를 제거할 예정이에요.

Module#const_set 메서드는 영향을 받지 않아요.

범위 (Scope)

이 지시문은 같은 파일 안에서 여러 번 사용할 수 있어요.

# shareable_constant_value: none
A = {foo: []}
A.frozen? # => false
Ractor.new { puts A } # => can not access non-shareable objects by non-main Ractor.

# shareable_constant_value: literal
B = {foo: []}
B.frozen? # => true
B[:foo].frozen? # => true

C = [Object.new] # => cannot assign unshareable object to C (Ractor::IsolationError)

D = [Object.new.freeze]
D.frozen? # => true

# shareable_constant_value: experimental_everything
E = Set[1, 2, Object.new]
E.frozen? # => true
E.all(&:frozen?) # => true

지시문은 그 뒤에 오는 상수와 현재 스코프에만 영향을 줘요.

module Mod
  # shareable_constant_value: literal
  A = [1, 2, 3]
  module Sub
    B = [4, 5]
  end
end

C = [4, 5]

module Mod
  D = [6]
end
p Mod::A.frozen?, Mod::Sub::B.frozen? # => true, true
p C.frozen?, Mod::D.frozen? # => false, false

더 알아보기 (Learn more)

  • 리터럴 (literals) — 코드가 해석에 사용하는 값의 형태
  • Ractorshareable_constant_value 지시문이 다루는 공유 가능한 객체
  • 명령줄 옵션--enable=frozen-string-literal 등 실행 시 동작을 바꾸는 옵션