4. 함수 개요
이 장에서는 함수와 그 변형인 클로저, 메서드, 코루틴을 정의합니다. 본 문서는 세부 규칙으로 들어가기에 앞서 큰 그림을 그리는 데 목적이 있습니다.
목차
- 4.1. 함수 선언
- 4.2. 매개변수
- 4.3. 반환
- 4.4. 함수 타입
- 4.5. 클로저
- 4.6. 캡처 의미론
- 4.7.
ref키워드 - 4.8. 트레일링 클로저
- 4.9. 메서드
- 4.10. 중첩 함수
- 4.11. 제너레이터 (
yield) - 4.12. 비동기 (
async와await) - 4.13. 스트림 (
async yield) - 4.14.
throws시그니처 - 4.15.
unsafe(함수 modifier 와 블록 표현식) - 4.16. 진입점 (
main.rok,fn main, top-level 문)
함수의 역할
함수는 입력 값을 받아 결과 값을 만드는 코드 단위입니다. rok 의 함수는 다음과 같은 성격을 모두 가질 수 있습니다.
먼저 이름의 유무에 따라 구분됩니다. fn name(...) 으로 명명된 함수가 있고, |...| 으로 표현되는 익명 클로저가 있습니다. 다음으로 타입 명시 방식에 따라 정적 또는 점진적인 형태를 띱니다. 매개변수에 타입을 적으면 정적이고, 빠뜨리면 호출 지점에서 제네릭 단형화가 일어납니다 (any 슬롯, 제네릭). 또한 실행 모델 측면에서는 동기, 비동기, 생성자 형태가 모두 가능합니다. 일반 함수, async fn (Future 반환), yield fn (Iterator 반환), async yield fn (Stream 반환) 이 이에 해당합니다. 예외 처리의 관점에서는 잡힐 수 있거나 던지는 함수로 나뉩니다. 시그니처에 throws 를 두면 예외를 던질 수 있습니다 (에러 제어 흐름). 마지막으로 소속에 따라 메서드와 자유 함수로 구분됩니다. impl 블록 안에서 정의되어 어떤 타입에 부속되거나, 모듈 수준의 자유 함수로 존재합니다.
두 형태의 함수 정의
함수는 크게 두 가지 형태로 정의됩니다.
명명된 함수
먼저 이름을 가진 함수입니다.
fn area(r: f64) -> f64 {
3.14159 * r * r
}
이 형태는 fn 키워드와 이름, 매개변수, 반환 타입, 본체로 구성됩니다. 모듈 수준이나 impl 블록 안에 정의할 수 있습니다.
익명 클로저
다음으로 이름이 없는 클로저입니다.
|x: f64| -> f64 { 3.14159 * x * x }
|x| x * 2
|| compute()
이 형태는 |...| 로 매개변수 묶음을 둘러싸고 그 뒤에 본체를 적습니다. 표현식 위치에 직접 등장하며, 변수에 대입하거나 함수 인자로 그대로 전달할 수 있습니다.
두 형태는 내부에서 같은 메커니즘으로 처리됩니다. 차이는 이름이 있는지, 그리고 클로저를 만들 때 환경을 캡처하는지뿐입니다.
호출
함수와 클로저 모두 () 로 호출합니다.
let a = area(2.0f64);
let f = |x: f64| x * 2;
let b = f(3.0f64);
호출 시 인자가 매개변수에 매칭됩니다. 정확한 매칭 규칙은 매개변수 에서 정의합니다.
반환
본체의 마지막 식의 값이 함수의 반환값이 됩니다. 블록 표현식 의 블록 규칙을 함수 본체가 그대로 따릅니다.
fn add(x: i32, y: i32) -> i32 { x + y }
fn early(x: i32) -> i32 {
if x < 0 { return 0; } // 조기 종료에는 명시적 return
x * 2
}
따라서 return 키워드는 필수가 아닙니다. 마지막 식이 자동으로 반환됩니다. 자세한 규칙은 반환 에서 다룹니다.
함수와 표현식
함수와 클로저는 값입니다. 그래서 변수에 대입할 수 있고, 다른 함수에 인자로 넘길 수 있으며, 컬렉션에 넣을 수도 있습니다.
let f = |x| x * 2;
let v = list.map(f);
let g = if cond { f } else { |x| x + 1 };
함수의 타입은 함수 타입 에서 정의하며, 매개변수 타입과 반환 타입으로 이루어집니다.
호출 디스패치
함수 호출의 디스패치는 트레잇-as-타입 금지 의 원칙에 따라 vtable 간접 호출 없이 해소됩니다.
명명된 함수의 경우 정적으로 결정된 함수 주소로 직접 호출이 이루어집니다. 클로저는 캡처된 환경과 코드 포인터의 묶음이며, 호출은 캡처된 환경을 첫 인자로 넘기는 직접 호출 형태가 됩니다. 제네릭 함수는 제네릭 의 단형화를 거쳐 호출 지점마다 직접 호출로 풀립니다.
이처럼 함수와 클로저 호출은 원칙상 직접 호출이며 인라이닝이 가능합니다. 따라서 성능은 일반 메서드 호출과 동등합니다.