호출 표현식

호출 표현식 (Call Expression)

함수를 호출하는 건 Rust에서 너무 흔해서 문법을 따로 생각하지 않을 때가 많아요. 그런데 함수가 아닌 타입에 대해서도 f(...) 형태가 동작하는 걸 보면, Rust의 호출 규칙이 단순해 보이면서도 은근히 정교하다는 걸 느끼게 됩니다. 호출 표현식이 어떻게 정의되어 있는지 살펴볼게요.

출처: Rust Reference

문법 (Syntax)

CallExpression → Expression ( CallParams? )
CallParams     → Expression ( , Expression )* ,?

호출 표현식(call expression) 은 함수를 호출합니다. 이때 문법은 function operand(함수 피연산자)라고 부르는 표현식 하나에, argument operands(인자 피연산자)라고 부르는 괄호로 묶인 콤마 구분 표현식 목록이 따라붙는 형태예요.

함수가 결국 반환하면 그때 호출 표현식이 완료됩니다.

함수가 아닌 타입에 대해서는 f(...) 표현식이 function operand에 따라 다음 트레잇 중 하나의 메서드를 사용합니다:

필요하면 자동 borrow(대여)가 일어납니다. function operand는 또한 필요에 따라 자동으로 역참조(automatic dereference)되기도 합니다.

호출 표현식의 예시를 볼게요.

#![allow(unused)]
fn main() {
fn add(x: i32, y: i32) -> i32 { 0 }
let three: i32 = add(1i32, 2i32);
let name: &'static str = (|| "Rust")();
}

함수 호출 명확화 (Disambiguating Function Calls)

모든 함수 호출은 더 명시적인 fully-qualified 문법의 설탕(sugar)입니다.

함수 호출은 스코프 안의 아이템에 비추어 호출이 얼마나 모호한지에 따라 fully-qualified가 필요할 수 있습니다.

참고 과거에 "Unambiguous Function Call Syntax", "Universal Function Call Syntax", 또는 "UFCS"라는 용어가 문서, 이슈, RFC, 그리고 다른 커뮤니티 글들에서 쓰여 왔어요. 하지만 이 용어들은 서술력이 부족하고 오히려 논점을 흐릴 수 있습니다. 검색 가능성을 위해 여기서 언급할게요.

메서드나 연관 함수 호출의 수신자(receiver)나 참조 대상에 대한 모호함을 일으키는 상황이 자주 생깁니다. 그 예로는:

  • 스코프 안의 여러 트레잇이 같은 타입에 대해 같은 이름의 메서드를 정의하는 경우
  • auto-deref가 바람직하지 않은 경우, 예를 들어 스마트 포인터 자체의 메서드와 포인터가 가리키는 대상의 메서드를 구분해야 하는 상황
  • 인자를 받지 않는 메서드, 예를 들어 default()나 타입의 프로퍼티를 반환하는 size_of() 같은 경우

이런 모호함을 해결하려면 프로그래머가 원하는 메서드나 함수를 더 구체적인 경로·타입·트레잇으로 지칭하면 됩니다.

예를 들어 볼게요.

trait Pretty {
    fn print(&self);
}

trait Ugly {
    fn print(&self);
}

struct Foo;
impl Pretty for Foo {
    fn print(&self) {}
}

struct Bar;
impl Pretty for Bar {
    fn print(&self) {}
}
impl Ugly for Bar {
    fn print(&self) {}
}

fn main() {
    let f = Foo;
    let b = Bar;

    // we can do this because we only have one item called `print` for `Foo`s
    f.print();
    // more explicit, and, in the case of `Foo`, not necessary
    Foo::print(&f);
    // if you're not into the whole brevity thing
    <Foo as Pretty>::print(&f);

    // b.print(); // Error: multiple 'print' found
    // Bar::print(&b); // Still an error: multiple `print` found

    // necessary because of in-scope items defining `print`
    <Bar as Pretty>::print(&b);
}

Foo 하나에는 print가 하나뿐이라 f.print()로 호출할 수 있지만, Bar에는 PrettyUgly 두 트레잇이 같은 이름의 print를 정의하므로 <Bar as Pretty>::print(&b)처럼 명시해야 해요.

자세한 내용과 동기는 RFC 132를 참고하세요.

더 알아보기 (Learn more)