파일 불러오기

파일 불러오기 (Requiring files)

프로그램이 커지면 한 파일에 모든 걸 담는 건 유지보수가 어려워져요. Crystal에서는 require로 다른 파일을 불러와 여러 파일로 프로그램을 나눌 수 있어요.

출처: Crystal 공식 문서

본문

작은 조각이나 벤치마크 코드는 한 파일로 충분해요. 하지만 큰 프로그램은 여러 파일로 나눠야 유지보수하고 이해하기 좋아요.

컴파일러가 다른 파일을 처리하게 하려면 require "..."를 써요. 단일 인자(문자열 리터럴)를 받고, 여러 형태가 있어요.

파일이 한 번 require되면 컴파일러가 그 절대 경로를 기억하고, 나중에 같은 파일을 require하는 것은 무시돼요.

require "filename"

이것은 require 경로에서 "filename"을 찾아요.

기본적으로 require 경로는 두 곳을 포함해요.

  • 현재 작업 디렉토리 기준 lib 디렉토리(여기서 의존성을 찾아요)
  • 컴파일러에 포함된 표준 라이브러리의 위치

이 두 곳만 검색돼요.

컴파일러가 사용하는 정확한 경로는 crystal env CRYSTAL_PATH로 조회할 수 있어요.

$ crystal env CRYSTAL_PATH
lib:/usr/bin/../share/crystal/src

이 검색 경로는 CRYSTAL_PATH 환경 변수를 정의해서 덮어쓸 수 있어요.

검색은 이렇게 진행돼요.

  • require 경로에서 "filename.cr"라는 파일이 발견되면 그것이 require돼요.
  • "filename"이라는 디렉토리가 발견되고, 그 바로 아래에 "filename.cr" 파일이 있으면 require돼요.
  • "filename"이라는 디렉토리에 "src" 디렉토리가 있고 그 바로 아래에 "filename.cr" 파일이 있으면 require돼요.
  • 그렇지 않으면 컴파일 타임 오류가 발생해요.

두 번째 규칙은 이런 구성 외에도:

- project
  - src
    - file
      - sub1.cr
      - sub2.cr
    - file.cr (requires "./file/*")

이렇게도 쓸 수 있다는 뜻이에요.

- project
  - src
    - file
      - file.cr (requires "./*")
      - sub1.cr
      - sub2.cr

취향에 따라 조금 더 깔끔할 수 있죠.

세 번째 규칙은 프로젝트의 전형적인 디렉토리 구조 덕분에 매우 편리해요.

- project
  - lib
    - foo
      - src
        - foo.cr
    - bar
      - src
        - bar.cr
  - src
    - project.cr
  - spec
    - project_spec.cr

즉, "lib/{project}" 안에 각 프로젝트의 디렉토리(src, spec, README.md 등)가 존재해요.

예를 들어 project.crrequire "foo"를 넣고 프로젝트 루트에서 crystal src/project.cr를 실행하면, lib/foo/foo.cr에서 foo를 찾아요.

네 번째 규칙은 세 번째 규칙에 두 번째 규칙을 적용한 거예요.

컴파일러를 다른 곳, 예를 들어 src 폴더에서 실행하면 lib가 경로에 없어서 require "foo"를 해석할 수 없어요.

require "./filename"

이것은 require 표현식을 담은 파일을 기준으로 "filename"을 찾아요.

검색은 이렇게 진행돼요.

  • 현재 파일 기준으로 "filename.cr" 파일이 발견되면 require돼요.
  • "filename" 디렉토리가 발견되고 그 바로 아래에 "filename.cr" 파일이 있으면 require돼요.
  • 그렇지 않으면 컴파일 타임 오류가 발생해요.

이 상대 경로는 주로 프로젝트 안에서 다른 파일을 참조할 때 써요. spec에서 코드를 참조할 때도 사용돼요.

require "../src/project"

다른 형태

두 경우 모두 중첩 이름을 쓸 수 있고 중첩 디렉토리에서 검색돼요.

  • require "foo/bar/baz"는 require 경로에서 "foo/bar/baz.cr", "foo/bar/baz/baz.cr", "foo/src/bar/baz.cr" 또는 "foo/src/bar/baz/baz.cr"을 찾아요.
  • require "./foo/bar/baz"는 현재 파일 기준으로 "foo/bar/baz.cr" 또는 "foo/bar/baz/baz.cr"을 찾아요.

현재 파일 기준으로 상위 디렉토리에 접근하려면 "../"를 쓸 수도 있어서, require "../../foo/bar"도 동작해요.

이 모든 경우에 특별한 *** 접미사를 쓸 수 있어요.

  • require "foo/*"는 "foo" 디렉토리 아래의 모든 ".cr" 파일을 require하지만, "foo" 안의 하위 디렉토리는 포함하지 않아요.
  • require "foo/**"는 "foo" 디렉토리 아래와 "foo" 안의 하위 디렉토리 아래의 모든 ".cr" 파일을 재귀적으로 require해요.

더 알아보기

  • 파일을 한 번 require하면 컴파일러가 절대 경로를 기억하므로, 같은 파일을 여러 번 require해도 한 번만 처리돼요.
  • lib는 의존성, 표준 라이브러리는 컴파일러가 기본 검색 경로로 사용한다는 점을 기억하면 돼요.