매개변수와 반환값
함수를 작성할 때 가장 중요한 요소 중 하나는 매개변수(parameter)와 반환값(return value)이다. 러스트에서 매개변수와 반환값을 정의할 때는 엄격한 타입이 요구된다. 함수가 어떤 데이터를 입력받고 어떤 데이터를 결과로 내보내는지 명확하게 명시해야만 컴파일 단계에서 안정성을 보장할 수 있다. 이러한 설계 방식은 실행 중에 발생할 수 있는 많은 오류를 미연에 방지해 준다.
러스트의 함수는 기본적으로 fn 키워드를 사용하여 정의하며, 소괄호 안에 함수가 받을 매개변수를 선언하고, 화살표(->) 뒤에 반환 타입을 명시한다. 매개변수와 반환값의 타입을 명시하지 않으면 컴파일 오류가 발생하기 때문에, 항상 타입을 함께 써 주어야 한다. 예를 들어, 두 개의 정수 값을 더한 뒤 그 결과를 반환하는 함수는 아래와 같이 작성할 수 있다.
fn add(a: i32, b: i32) -> i32 {
a + b
}위 예시에서 함수 add는 i32 타입 매개변수 두 개 a, b를 입력받고, 이 둘을 더한 값 a + b를 반환한다. 마지막 줄의 표현식이 세미콜론 없이 끝났기 때문에, 이는 함수의 반환값이 된다. 만약 마지막 줄이 세미콜론으로 끝나면 문장이 되어 값을 반환하지 않으므로, 일부러 반환값을 명시해 주거나, 혹은 블록 안에서 return 키워드를 이용해 미리 반환해야 한다.
함수는 코드 블록의 가장 마지막 표현식을 반환하지만, 필요할 경우 중간에 return 키워드를 사용하여 명시적으로 반환할 수 있다. 다만 러스트의 함수에서는 보통 마지막 표현식 형태로 반환하는 방법이 더 자주 사용된다. 이는 문장(statement)과 표현식(expression)이 엄격하게 구분되는 러스트 특유의 문법 때문에 가능하다. 문장 끝에 세미콜론이 있으면 값을 반환하지 않는다는 점에 주의해야 한다.
매개변수를 전달할 때는 필요한 경우 참조(reference)를 사용할 수도 있다. 참조를 사용하면 소유권을 이전하지 않고서도 함수가 외부 데이터를 읽거나 수정할 수 있다. 예를 들어, 어떤 문자열의 길이를 구하는 함수를 만들 때, 문자열 데이터의 소유권을 넘겨주지 않고 참조만 건네주도록 설계할 수 있다.
fn length_of_string(s: &String) -> usize {
s.len()
}위 함수 length_of_string은 문자열에 대한 참조 &String을 매개변수로 받아서, 문자열의 길이를 usize로 반환한다. 이 때 소유권은 여전히 원본 문자열을 가지고 있는 쪽에 남게 된다. 만약 함수 내부에서 해당 문자열을 변경하고 싶다면 가변 참조(&mut String)를 사용할 수 있는데, 이 경우 호출하는 측에서도 문자열에 대한 가변 참조를 허용해야 한다.
반환값에도 참조가 올 수 있는데, 이 경우에는 생애(lifetime)를 고려해야 한다. 함수에서 생성한 값을 참조 형태로 반환한다면, 그 값은 함수가 끝남과 동시에 스택 프레임에서 해제될 수도 있으므로 안전하지 않다. 따라서 함수 밖에서 유효한 데이터를 참조로 반환하려면, 반드시 반환하려는 데이터가 함수 외부의 스코프에서 유효하도록 설계해야 한다. 그렇지 않으면 컴파일러가 빌드를 허용하지 않는다. 러스트는 이러한 상황을 컴파일 타임에 점검하기 위해 생애 파라미터(lifetime parameter)를 사용한다.
복수 개의 값을 반환해야 할 때는 튜플(tuple)을 많이 사용한다. 러스트는 함수가 여러 개의 값을 직접 반환하는 문법을 제공하지 않는 대신, 여러 데이터를 하나의 구조로 묶어 반환하는 튜플을 활용하는 방식이 일반적이다. 아래 예시는 두 정수의 합과 차를 동시에 반환하고 있다.
fn sum_and_diff(a: i32, b: i32) -> (i32, i32) {
(a + b, a - b)
}반환된 튜플은 (합, 차)의 형태이며, 호출 측에서 let (s, d) = sum_and_diff(10, 3) 같은 식으로 받을 수 있다. 이렇게 하면 s에는 13이, d에는 7이 바인딩된다.
러스트의 함수 시그니처에는 예외적으로 반환 타입을 적지 않아도 되는 상황이 하나 있는데, 그것은 함수가 영원히 종료되지 않는 경우다. 예를 들어 panic!을 일으키거나 프로그램을 영구적으로 멈추는 코드가 있다면, 해당 함수는 ! (never type)로 표시될 수 있다. 그러나 일반적으로는 대부분의 함수가 정상적인 흐름 내에서 실행을 마치므로, 반환 타입을 생략하지 않고 명시하는 것이 권장된다.
함수를 작성할 때는 가능한 한 매개변수와 반환값의 타입을 명확하고 좁은 범위로 설정해 주는 것이 바람직하다. 이는 코드의 의도를 분명하게 드러내고, 컴파일러가 더 많은 최적화를 수행할 수 있도록 도와준다. 또한 함수가 너무 많은 일을 하지 않도록 잘게 쪼개고, 각 함수가 명확한 책임을 가지게 만들면, 프로그램 전체 구조를 더 견고하고 안전하게 설계할 수 있다.
매개변수를 정의하고 적절한 반환값을 설정하는 과정은 러스트 프로그래밍에서 매우 핵심적이다. 이는 함수가 데이터를 어떻게 다루고, 결과를 어떤 형태로 되돌려 주는지 표현하기 때문이다. 코드의 유지보수성, 가독성, 안전성 모두가 매개변수와 반환값의 명료한 설계를 통해 크게 향상될 수 있다. 특히 소유권과 생애에 대한 러스트의 규칙을 준수하여 함수를 작성하면, 실행 중 오류를 최소화하고 논리적 안정성을 극대화할 수 있다.
Last updated