발산(Divergence)
발산(Divergence)
발산 표현식(diverging expression)이란 정상적인 실행을 끝내지 않는 표현식을 말해요. 코드로 보면 바로 와닿을 거예요.
#![allow(unused)]
fn main() {
fn diverges() -> ! {
panic!("This function never returns!");
}
fn example() {
let x: i32 = diverges(); // 이 줄은 끝까지 실행되지 않아요.
println!("This is never printed: {x}");
}
}
여기서 diverges()는 panic! 때문에 결코 값을 반환하지 않으니, 그 뒤의 println!은 영원히 실행되지 않죠. 이런 표현식을 발산한다고 해요.
각 표현식이 어떤 조건에서 발산하는지는 다음 규칙을 참고하면 돼요.
- 블록 표현식 — 블록이 끝까지 도달하지 않는 경우
if표현식 — 모든 분기가 발산하는 경우break가 있는 레이블 블록 표현식break가 있는loop표현식break표현식continue표현식- 무한
loop표현식 match표현식 — 팔(arm) 중 어느 것도 값에 도달하지 않는 경우- 빈
match표현식 return표현식!를 반환하는 함수 호출
참고
panic!매크로와unreachable!처럼 panic을 일으키는 관련 매크로도 타입이!이고 발산해요.
출처: Rust Reference
본문
언제 발산한다고 말하나요
타입이 !인 표현식은 모두 발산 표현식이에요. 그런데 발산 표현식이 ! 타입으로만 제한되는 건 아니에요. 예를 들어 Some(loop {})은 타입이 Option<!>이지만 그 자체로 발산하는 표현식이죠.
여기서 헷갈리기 쉬운 부분이 하나 있어요. !는 비어 있는 타입(uninhabited type)으로 간주되지만, 타입이 비어 있다고 해서 반드시 발산하는 건 아니에요. 아래 예시를 볼게요.
#![allow(unused)]
fn main() {
enum Empty {}
fn make_never() -> ! {loop{}}
fn make_empty() -> Empty {loop{}}
fn diverging() -> ! {
// 이 표현식은 타입이 `!`예요.
// 그래서 함수 전체가 발산하는 것으로 간주돼요.
make_never();
// OK: 본문의 타입이 `!`로 반환 타입과 일치해요.
}
fn not_diverging() -> ! {
// 이 타입은 비어 있어요(uninhabited).
// 하지만 함수 전체는 발산하는 것으로 간주되지 않아요.
make_empty();
// ERROR: 본문 타입이 `()`인데 기대 타입은 `!`예요.
}
}
make_never()처럼 타입이 !인 호출은 발산으로 보고, make_empty()처럼 Empty 타입은 비어 있지만 !가 아니므로 발산으로 보지 않아요. 그래서 not_diverging은 반환 타입 !와 본문 타입 ()이 맞지 않아 컴파일 오류가 나죠.
발산은 주변 블록으로 퍼져나갈 수도 있어요.
후보(fallback) 타입 추론
추론해야 할 타입이 오직 발산 표현식하고만 통일(unified)된다면, 그 타입은 !로 추론돼요.
#![allow(unused)]
fn main() {
fn foo() -> i32 { 22 }
match foo() {
// ERROR: 트레잇 바운드 `!: Default`를 만족하지 못해요.
4 => Default::default(),
_ => return,
};
}
여기서 _ => return 분기가 발산이라서, match 전체의 타입을 !로 추론하려고 해요. 그런데 다른 분기에서 Default::default()를 요구하니 !가 Default 트레잇을 만족해야 하는데 그러지 못해서 오류가 나는 거예요.
2024 에디션 차이
2024 에디션 이전에는 이 경우 타입이
()로 추론됐어요.
중요한 점은 타입 통일이 구조적으로 일어날 수 있다는 거예요. 그래서 후보 !가 더 큰 타입의 일부가 될 수도 있어요. 아래 코드는 컴파일되는데, _ => Some(return) 때문에 match 전체가 Option<!>가 되거든요.
#![allow(unused)]
fn main() {
fn foo() -> i32 { 22 }
// 이 표현식은 `!`가 아니라 `Option<!>` 타입이에요.
match foo() {
4 => Default::default(),
_ => Some(return),
};
}
더 알아보기
!(never) 타입: 발산의 기반이 되는 타입- 블록,
if,loop,match,return표현식의 각 발산 규칙 - panic과 관련 매크로의 동작