코드 블록과 표현식

러스트에서 코드 블록은 중괄호로 둘러싸인 영역으로, 새로운 스코프(scope)를 형성하고 여러 문(statement)과 표현식(expression)을 포함할 수 있는 기본적인 구조다. 함수의 몸체 또한 코드 블록이며, if, match, 반복문 같은 제어 흐름 구문 역시 내부에서 코드 블록을 사용한다. 코드 블록이 러스트에서 특별한 이유는, 러스트가 표현식 중심(expression-oriented) 언어이기 때문이다. 이러한 특성 덕분에 코드 블록 자체가 값(value)을 반환할 수 있고, 마지막 표현식의 결과를 바탕으로 변수 초기화나 함수 반환값 등을 결정할 수 있다.

러스트에서 표현식(expression)과 문(statement)을 구분하는 것은 매우 중요하다. 문은 작업을 수행하지만 값을 생성하지 않으며, 표현식은 특정 값을 생성한다. 예를 들어, 변수 선언문이나 세미콜론으로 끝나는 구문은 문에 해당한다. 반면에 최종적으로 값을 반환하는 식들은 표현식으로 간주된다. 코드 블록은 문들을 포함하지만, 마지막 줄에 세미콜론이 없는 표현식이 올 경우 해당 표현식의 값을 블록의 결과값으로 삼는다. 이것은 함수의 반환과도 유사한 개념으로 작동한다.

다음 예제 코드를 살펴보면, 코드 블록이 어떻게 표현식 역할을 하여 값을 반환하는지 알 수 있다.

fn main() {
    let x = {
        let y = 3;
        y + 1
    };
    
    println!("x의 값은 {}", x);
}

위 코드에서 let x = { ... } 부분은 코드 블록을 통해 x 변수를 초기화한다. 내부에서 y를 선언하고, 최종적으로 y + 1이라는 표현식을 세미콜론 없이 두어 이 값이 블록의 결과로 반환된다. 따라서 x는 4를 갖게 되며, 이후 println! 매크로로 값이 출력된다. 만약 y + 1 뒤에 세미콜론을 붙였다면 y + 1은 문(statement)으로 취급되고, 블록 안에 별도의 반환 표현식이 없으므로 블록 전체의 결과값은 ()(유닛 타입)이 되었을 것이다.

또한 코드 블록은 새로운 스코프를 형성한다. 블록 내부에서 선언된 변수는 블록 안에서만 유효하며, 블록을 벗어나면 해당 변수는 더 이상 사용할 수 없다. 이것은 변수의 사용 범위를 명확히 구분하고, 메모리 관리를 안전하게 해준다.

아래 예제에서는 함수 내부에 중첩된 블록을 사용하여 변수의 가시성 범위(scalar scope)가 어떻게 달라지는지 확인할 수 있다.

fn main() {
    let a = 10;

    {
        let b = 20;
        println!("블록 내부에서 a={}, b={}", a, b);
    }

    println!("블록 외부에서 a={}", a);
    // println!("b={}", b); // 이 줄은 에러를 발생시킨다
}

이 예제에서 내부 블록 안에서는 바깥쪽 스코프의 a와 블록 내부에서 선언된 b를 모두 사용할 수 있다. 하지만 블록을 벗어난 뒤에는 b가 사라져 외부에서 접근하면 컴파일 오류가 발생한다. 이렇게 코드 블록을 통해 구문을 영역별로 묶고, 필요한 변수만 제한적인 영역에서 사용하도록 제어할 수 있다.

러스트가 표현식 중심 언어라는 점을 염두에 두면, 다양한 제어 흐름 구문에서도 표현식이 널리 쓰인다는 사실을 이해하기 쉽다. 예를 들어 if 구문은 다른 언어에서는 주로 문으로 취급되지만, 러스트에서는 if 구문이 값(표현식)을 반환할 수 있다. 따라서 조건에 따라 다른 값을 생성해낼 수 있으며, 중괄호로 둘러싸인 코드 블록을 통해 각각의 분기에서 다른 표현식을 수행할 수 있다.

위 예제에서 if 블록과 else 블록은 각각 100, 200을 반환하는 표현식이 되며, 그 중 하나가 최종적으로 result 변수에 할당된다. 다른 언어에서는 삼항 연산자(조건 연산자) 등을 사용해야 할 상황을 러스트에서는 직접 if 표현식을 통해 명확히 구현할 수 있다.

함수 정의 내부도 실제로는 코드 블록으로 둘러싸여 있으며, 함수 몸체에서 마지막에 나타나는 표현식은 함수의 반환값이 된다. return 키워드를 사용해 명시적으로 값을 반환할 수도 있지만, 러스트에서는 블록의 마지막에 세미콜론 없이 표현식을 배치하면 그 값이 자동으로 반환된다. 이 방식은 함수의 결과를 더 명확하고 간결하게 표현할 수 있게 한다.

위와 같은 코드에서 add 함수는 a + b를 반환하는 표현식을 사용한다. return 키워드를 명시하지 않고도, a + b가 세미콜론 없이 함수의 마지막 줄에 위치하므로 함수의 반환값으로 취급된다. 만약 세미콜론을 붙인다면 a + b는 문(statement)이 되고, 함수 몸체는 결과적으로 ()(유닛 타입)을 반환하게 되므로 함수 원형에 명시된 반환 타입 i32와 맞지 않아 컴파일 에러가 발생한다.

이처럼 러스트에서는 문과 표현식의 구분, 그리고 코드 블록이 표현식으로 동작할 수 있다는 점이 매우 중요하다. 코드 블록은 단순히 코드를 묶고 스코프를 형성하는 역할을 넘어서, 블록 자체가 값을 가지는 표현식이 될 수 있다. 이러한 개념은 함수와 모듈을 설계할 때 필수적인 이해 요소가 되며, 안전하면서도 간결한 스타일로 프로그램을 구성하게 해준다.

Last updated