스코프(Scopes)

스코프(Scopes)

스코프는 이름 붙은 개체가 그 이름으로 참조될 수 있는 소스 텍스트 영역이에요. 개체의 종류와 어디에 선언됐는지에 따라 스코프 규칙·동작이 달라지는데, 이 장에서 그 세부를 다뤄요. 이름이 어떻게 개체로 해석되는지는 이름 해석 챕터에서 설명해요. 파괴자(destructor)를 실행하는 "드롭 스코프"에 대한 내용은 파괴자 챕터에서 확인할 수 있어요.

출처: Rust Reference

본문

아이템 스코프

모듈에 직접 선언된 아이템의 이름은 모듈의 시작부터 끝까지 뻗는 스코프를 가져요. 이런 아이템들은 모듈의 멤버이기도 해서, 모듈에서 시작하는 경로로 참조할 수 있어요.

문장(statement)으로 선언된 아이템의 이름은 그 아이템 문장이 있는 블록의 시작부터 블록 끝까지 뻗는 스코프를 가져요.

같은 모듈이나 블록 안의 같은 네임스페이스에서 다른 아이템과 중복된 이름의 아이템을 도입하면 오류예요. 별표 글롭 가져오기는 중복 이름과 섀도잉을 다루는 특별한 동작이 있어요.

모듈의 아이템은 프렐류드의 아이템을 섀도잉할 수 있어요. 또 바깥 모듈의 아이템 이름은 중첩된 모듈 안에서는 스코프에 있지 않아요. 다른 모듈의 아이템을 참조하려면 경로를 쓰면 돼요.

연관 아이템 스코프

연관 아이템은 스코프가 없어서, 연관된 타입이나 트레잇에서 시작하는 경로로만 참조할 수 있어요. 메서드는 호출 표현식으로도 참조할 수 있죠.

모듈이나 블록 안의 아이템처럼, 트레잇이나 구현 안에서 같은 네임스페이스의 다른 아이템과 중복되는 아이템을 도입하면 오류예요.

패턴 바인딩 스코프

로컬 변수 패턴 바인딩의 스코프는 어디서 쓰이느냐에 따라 달라져요.

  • let 문 바인딩: let 문 바로 뒤부터 선언된 블록 끝까지
  • 함수 매개변수 바인딩: 함수 본문 안에서
  • 클로저 매개변수 바인딩: 클로저 본문 안에서
  • for 바인딩: 루프 본문 안에서
  • if letwhile let 바인딩: 이어지는 조건들과 결과 블록에서 유효해요
  • match 팔 바인딩: match 가드와 match 팔 표현식 안에서
  • match 가드 let 바인딩: 이어지는 가드 조건과 match 팔 표현식에서 유효해요

로컬 변수 스코프는 아이템 선언 안으로는 뻗지 않아요.

패턴 바인딩 섀도잉

패턴 바인딩은 스코프의 어떤 이름이든 섀도잉할 수 있는데, 다음 예외는 오류예요.

  • const 제네릭 매개변수
  • 정적(static) 아이템
  • const 아이템
  • 구조체와 열거형의 생성자

아래 예시는 로컬 바인딩이 아이템 선언을 어떻게 섀도잉하는지 보여줘요.

#![allow(unused)]
fn main() {
fn shadow_example() {
    // 아직 스코프에 로컬 변수가 없으므로, 이건 함수로 해석돼요.
    foo(); // `function` 출력
    let foo = || println!("closure");
    fn foo() { println!("function"); }
    // 아이템을 섀도잉하므로, 이건 로컬 클로저로 해석돼요.
    foo(); // `closure` 출력
}
}

제네릭 매개변수 스코프

제네릭 매개변수는 GenericParams 목록에 선언돼요. 제네릭 매개변수의 스코프는 그것이 선언된 아이템 안이에요.

모든 매개변수는 선언된 순서와 무관하게 제네릭 매개변수 목록 안에서 스코프에 있어요. 아래 예시는 매개변수가 선언되기 전에 참조되는 경우를 보여줘요.

#![allow(unused)]
fn main() {
// 'b 바운드는 선언되기 전에 참조돼요.
fn params_scope<'a: 'b, 'b>() {}

trait SomeTrait<const Z: usize> {}
// const N은 선언되기 전에 트레잇 바운드에서 참조돼요.
fn f<T: SomeTrait<N>, const N: usize>() {}
}

제네릭 매개변수는 타입 바운드와 where 절에서도 스코프에 있어요. 아래 예시를 볼게요.

#![allow(unused)]
fn main() {
trait SomeTrait<'a, T> {}
// `SomeTrait`의 <'a, U>는 `bounds_scope`의 'a와 U 매개변수를 가리켜요.
fn bounds_scope<'a, T: SomeTrait<'a, U>, U>() {}

fn where_scope<'a, T, U>()
    where T: SomeTrait<'a, U>
{}
}

함수 안에 선언된 아이템이 바깥 스코프의 제네릭 매개변수를 참조하면 오류예요.

#![allow(unused)]
fn main() {
fn example<T>() {
    fn inner(x: T) {} // ERROR: 바깥 함수의 제네릭 매개변수를 쓸 수 없어요.
}
}

제네릭 매개변수 섀도잉

제네릭 매개변수를 섀도잉하면 오류예요. 단, 함수 안에 선언된 아이템이 함수의 제네릭 매개변수 이름을 섀도잉하는 것은 허용돼요.

#![allow(unused)]
fn main() {
fn example<'a, T, const N: usize>() {
    // 함수 안의 아이템은 스코프의 제네릭 매개변수를 섀도잉해도 돼요.
    fn inner_lifetime<'a>() {} // OK
    fn inner_type<T>() {} // OK
    fn inner_const<const N: usize>() {} // OK
}
}
#![allow(unused)]
fn main() {
trait SomeTrait<'a, T, const N: usize> {
    fn example_lifetime<'a>() {} // ERROR: 'a가 이미 사용 중이에요.
    fn example_type<T>() {} // ERROR: T가 이미 사용 중이에요.
    fn example_const<const N: usize>() {} // ERROR: N이 이미 사용 중이에요.
    fn example_mixed<const T: usize>() {} // ERROR: T가 이미 사용 중이에요.
}
}

수명 스코프

수명 매개변수는 GenericParams 목록과 고순위 트레잇 바운드(higher-ranked trait bound)에 선언돼요.

'static 수명과 플레이스홀더 수명 '_는 특별한 의미를 가져서 매개변수로 선언할 수 없어요.

수명 제네릭 매개변수 스코프: 상수·정적 아이템과 const 컨텍스트는 'static 수명 참조만 허용해요. 그래서 그 안에서는 다른 수명이 스코프에 있을 수 없어요. 연관 상수는 트레잇이나 구현에 선언된 수명을 참조하는 건 허용돼요.

고순위 트레잇 바운드 스코프: 고순위 트레잇 바운드로 선언된 수명 매개변수의 스코프는 사용되는 시나리오에 따라 달라져요.

  • TypeBoundWhereClauseItem으로 쓰이면, 선언된 수명이 타입과 타입 바운드에서 스코프에 있어요.
  • TraitBound으로 쓰이면, 선언된 수명이 바운드 타입 경로 안에서 스코프에 있어요.
  • BareFunctionType으로 쓰이면, 선언된 수명이 함수 매개변수와 반환 타입 안에서 스코프에 있어요.
#![allow(unused)]
fn main() {
trait Trait<'a>{}

fn where_clause<T>()
    // 'a는 타입과 타입 바운드 양쪽에서 스코프에 있어요.
    where for <'a> &'a T: Trait<'a>
{}

fn bound<T>()
    // 'a는 바운드 안에서 스코프에 있어요.
    where T: for <'a> Trait<'a>
{}

struct Example<'a> {
    field: &'a u32
}

// 'a는 매개변수와 반환 타입 양쪽에서 스코프에 있어요.
type FnExample = for<'a> fn(x: Example<'a>) -> Example<'a>;
}

impl 트레잇 제한: impl 트레잇 타입은 함수나 구현에 선언된 수명만 참조할 수 있어요.

#![allow(unused)]
fn main() {
trait Trait1 {
    type Item;
}
trait Trait2<'a> {}

struct Example;

impl Trait1 for Example {
    type Item = Element;
}

struct Element;
impl<'a> Trait2<'a> for Element {}

// 여기 `impl Trait2`는 'b를 참조할 수 없고 'a는 참조할 수 있어요.
fn foo<'a>() -> impl for<'b> Trait1<Item = impl Trait2<'a> + use<'a>> {
    // ...
   Example
}
}

루프 레이블 스코프

루프 레이블은 루프 표현식으로 선언될 수 있어요. 루프 레이블의 스코프는 선언된 지점부터 루프 표현식의 끝까지예요. 이 스코프는 아이템, 클로저, async 블록, const 인자, const 컨텍스트, 그리고 정의하는 for 루프의 반복자 표현식 안으로는 뻗지 않아요.

#![allow(unused)]
fn main() {
'a: for n in 0..3 {
    if n % 2 == 0 {
        break 'a;
    }
    fn inner() {
        // 여기서 'a를 쓰면 오류예요.
        // break 'a;
    }
}

// 레이블은 `while` 루프의 표현식에서 스코프에 있어요.
'a: while break 'a {}         // 루프가 실행되지 않아요.
'a: while let _ = break 'a {} // 루프가 실행되지 않아요.

// 레이블은 정의하는 `for` 루프 안에서는 스코프에 있지 않아요.
'a: for outer in 0..5 {
    // 바깥 루프를 깨고, 안쪽 루프를 건너뛰며 바깥 루프를 멈춰요.
    'a: for inner in { break 'a; 0..1 } {
        println!("{}", inner); // 실행되지 않아요.
    }
    println!("{}", outer); // 이것도 실행되지 않아요.
}

}

루프 레이블은 바깥 스코프의 같은 이름의 레이블을 섀도잉할 수 있어요. 레이블을 참조하면 가장 가까운 정의를 가리켜요.

#![allow(unused)]
fn main() {
// 루프 레이블 섀도잉 예시.
'a: for outer in 0..5 {
    'a: for inner in 0..5 {
        // 이건 안쪽 루프를 끝내지만, 바깥 루프는 계속 실행돼요.
        break 'a;
    }
}
}

프렐류드 스코프

프렐류드는 개체를 모든 모듈의 스코프로 가져와요. 그 개체들은 모듈의 멤버가 아니라, 이름 해석 중에 암묵적으로 조회돼요.

프렐류드 이름은 모듈의 선언으로 섀도잉될 수 있어요.

프렐류드는 같은 이름의 개체를 담고 있으면 서로를 섀도잉하도록 겹겹이 쌓여요. 프렐류드가 다른 프렐류드를 섀도잉할 수 있는 순서는 다음과 같아요. 앞 항목이 뒤 항목을 섀도잉해요.

  • 외부(extern) 프렐류드
  • 도구(tool) 프렐류드
  • macro_use 프렐류드
  • 표준 라이브러리 프렐류드
  • 언어(language) 프렐류드

macro_rules 스코프

macro_rules 매크로의 스코프는 매크로 예시(Macros By Example) 챕터에서 설명해요. 동작은 macro_usemacro_export 속성 사용 여부에 달려 있어요.

파생 매크로 헬퍼 속성(derive macro helper attributes)

파생 매크로 헬퍼 속성은 대응하는 derive 속성이 지정된 아이템에서 스코프에 있어요. 스코프는 derive 속성 바로 뒤부터 아이템 끝까지 뻗어요.

헬퍼 속성은 스코프의 같은 이름을 가진 다른 속성을 섀도잉해요.

Self 스코프

Self는 특별한 의미를 가진 키워드지만, 이름 해석에서는 일반 이름과 비슷하게 상호작용해요.

구조체, 열거형, 공용체, 트레잇, 구현의 정의에 있는 암묵적인 Self 타입은 제네릭 매개변수처럼 취급되고, 제네릭 타입 매개변수와 같은 방식으로 스코프에 있어요.

구현의 값 네임스페이스에 있는 암묵적인 Self 생성자는 구현의 본문(구현의 연관 아이템) 안에서 스코프에 있어요.

#![allow(unused)]
fn main() {
// 구조체 정의 안의 Self 타입.
struct Recursive {
    f1: Option<Box<Self>>
}

// 제네릭 매개변수 안의 Self 타입.
struct SelfGeneric<T: Into<Self>>(T);

// 구현 안의 Self 값 생성자.
struct ImplExample();
impl ImplExample {
    fn example() -> Self { // Self 타입
        Self() // Self 값 생성자
    }
}
}

더 알아보기

  • 이름 해석: 이름이 개체로 어떻게 해석되는지
  • 프렐류드의 종류와 섀도잉 순서
  • 파괴자와 드롭 스코프
  • 제네릭과 고순위 트레잇 바운드
  • 루프 표현식과 레이블