함수 정의와 호출

함수는 Rust 프로그램의 기본 구성 요소이며, 특정 로직을 독립된 블록으로 묶어 재사용하거나 프로그램을 구조화하기 위해 사용한다. Rust에서 함수는 키워드 fn으로 정의하며, 매개변수의 타입과 반환 타입을 명시해 주어야 한다. 모든 함수는 반환 타입을 생략할 수도 있지만, 그렇다면 암묵적으로 반환값이 없음을 의미한다. 함수 몸체는 중괄호로 둘러싸이며, 내부에는 로직을 구성하는 표현식이나 문장을 배치한다. Rust에서 표현식과 문장은 서로 구분되며, 표현식은 값을 반환하지만 문장은 값을 반환하지 않는다는 차이점이 있다.

함수를 정의할 때는 fn 키워드, 함수 이름, 괄호 안의 매개변수 목록, 그리고 반환 타입을 의미하는 기호 -> 를 작성한다. 만약 반환 타입이 없다면 -> 부분을 생략한다. 함수의 본문은 중괄호로 감싸며 내부에 실행할 코드를 작성한다. 함수의 마지막 표현식이 그 함수의 반환값이 되며, 세미콜론으로 끝나는 문장은 반환값이 되지 못한다. 이러한 규칙은 Rust가 표현식 중심 언어라는 점에서 비롯된다.

아래 예제는 간단한 함수를 정의하고 호출하는 방식이다.

fn main() {
    // say_hello 함수를 호출한다.
    say_hello();

    // add 함수는 i32 값을 두 개 받아서 i32 값을 반환한다.
    let result = add(3, 5);
    println!("결과: {}", result);
}

fn say_hello() {
    println!("안녕하라!");
}

fn add(x: i32, y: i32) -> i32 {
    x + y
}

위 코드에서 main 함수는 프로그램 진입점이며, say_hello 함수를 먼저 호출한 뒤 add 함수를 호출하고 그 결과값을 result 변수에 대입한다. add 함수는 x와 y라는 i32 타입 매개변수를 받고, 둘을 더한 값을 반환한다. 함수의 마지막 줄 x + y는 세미콜론이 없는 표현식이므로 add 함수의 반환값이 된다.

표현식과 문장의 구분은 Rust에서 매우 중요하다. x + y 같은 구문은 표현식이므로 값을 생성하며, 세미콜론을 붙이지 않는다. let result = add(3, 5); 같은 구문은 문장으로서, 결과값을 받아 result에 대입하고 세미콜론으로 끝난다. 함수 내부에서 최종적으로 반환하고 싶은 값을 표현식 형태로 남겨 두면 그 값이 반환된다.

함수 호출은 함수 이름 뒤에 괄호를 쓰고, 괄호 안에 매개변수 값을 순서대로 적어 준다. Rust에서는 각 매개변수마다 정확한 타입이 미리 정해져 있어야 하며, 인자 타입과 매개변수 타입이 일치하지 않으면 컴파일 에러가 발생한다. 이로써 컴파일 타임에 함수 호출의 타입 안정성을 보장받을 수 있다.

Rust에서 함수는 모듈 단위로 작성되며, 다른 모듈에 있는 함수를 호출하려면 모듈 경로를 통해 접근하거나 pub 키워드를 사용해 함수와 모듈을 공개 범위로 열어두어야 한다. 기본적으로 함수는 정의된 모듈 내부에서만 사용할 수 있다. 만약 외부 모듈에서도 함수에 접근해야 한다면 다음과 같이 pub 키워드를 붙여 주면 된다.

위 예제에서 multiply 함수는 pub 키워드로 공개되어 있으므로 math_utils 모듈 외부의 main 함수에서도 호출할 수 있다. 모듈의 외부 경로에서 접근할 때는 math_utils::multiply와 같이 :: 연산자로 모듈 경계를 구분한다.

함수를 작성할 때는 매개변수의 수와 타입에 맞춰서 정확히 인자를 전달해야 하며, Rust 컴파일러가 잘못된 함수를 호출하지 않도록 주의해야 한다. 함수의 본문은 명확하고 간결하게 작성하는 것이 유지보수에 도움이 되며, 반환 타입을 필요한 만큼 구체적으로 지정하면 의도를 더 분명하게 드러낼 수 있다.

함수의 반환 타입이 없거나, 혹은 단순히 종료를 의미하는 경우에는 리턴값이 ()인 함수로 간주된다. Rust에서 () 타입은 빈 튜플이라고 부르며, 실제로는 의미 있는 값이 없는 단위를 뜻한다. 함수의 매개변수 역시 필요 없으면 생략 가능하지만, 이 경우에도 매개변수 목록은 빈 괄호로 표기해 주어야 한다.

함수 정의는 코드의 중복을 줄이고 로직을 재사용하며, 모듈 간의 경계를 분명하게 하여 프로그램의 구조를 명확히 만들어 준다. Rust는 함수 파라미터와 반환 타입에서 매우 엄격한 타입 검사를 수행하며, 이를 통해 런타임 오류를 줄이고 안전성을 높일 수 있다. 이러한 특징 덕분에 함수 호출 시 발생할 수 있는 잘못된 타입 사용 문제를 컴파일 타임에 미리 잡아낼 수 있다.

함수와 모듈을 올바르게 설계해 두면 프로그램 전체의 복잡도가 감소하고, 각 기능이 어디에 위치하는지를 파악하기 쉬워진다. 함수 하나하나를 어떻게 설계하고 분리할지 결정하는 것은 Rust 코드를 체계적으로 관리하기 위한 중요한 습관이며, 모듈과 결합하여 높은 수준의 추상화 계층을 구축할 수 있다.

Last updated