홈강좌Rust 입문·중급 47편 중 31편째
Rust ? 연산자 — 실패하면 그 자리에서 Err 를 돌려준다
Rust 의 ? 는 Result 뒤에 붙여, Ok 면 안의 값을 꺼내고 Err 면 그 오류를 들고 함수를 바로 끝낸다. match 다섯 줄이 한 글자가 된다. ? 는 Result 를 돌려주는 함수 안에서만 쓸 수 있어서, 보통의 main 에서 쓰면 E0277 이 난다. main 이 Result 를 돌려주게 하면 된다.
01 match 로 오류를 넘기면 길다
문자열 숫자 둘을 받아 더하는 함수다. 둘 중 하나라도 숫자가 아니면 부른 쪽에 그 오류를 그대로 넘기고 싶다. 30편의 match 로 쓰면 이렇다.
use std::num::ParseIntError;
fn add(a: &str, b: &str) -> Result<i32, ParseIntError> {
let x: i32 = match a.parse() {
Ok(n) => n,
Err(e) => return Err(e),
};
let y: i32 = match b.parse() {
Ok(n) => n,
Err(e) => return Err(e),
};
Ok(x + y)
}
fn main() {
println!("{:?}", add("3", "4"));
println!("{:?}", add("3", "four"));
} Compiling total v0.1.0 (~/projects/total)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.17s
Running `target/debug/total`
Ok(7)
Err(ParseIntError { kind: InvalidDigit })- 반환 타입
Result<i32, ParseIntError>는 "성공하면i32, 실패하면ParseIntError" 다.ParseIntError는 정수parse가 내는 오류 타입이고, 맨 위use로 불러왔다(29편). parse마다match를 써서,Ok면 값을 꺼내고Err면return Err(e)로 함수를 끝낸다.- 둘 다 성공하면 마지막 줄
Ok(x + y)로 성공을 돌려준다.
동작은 맞지만, 실제로 하고 싶은 일(두 수 더하기)보다 오류를 넘기는 줄이 훨씬 길다.
02 ? 한 글자로 줄인다
match 다섯 줄을 ? 로 바꾼다.
use std::num::ParseIntError;
fn add(a: &str, b: &str) -> Result<i32, ParseIntError> {
let x: i32 = a.parse()?;
let y: i32 = b.parse()?;
Ok(x + y)
}
fn main() {
println!("{:?}", add("3", "4"));
println!("{:?}", add("3", "four"));
} Compiling total v0.1.0 (~/projects/total)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.16s
Running `target/debug/total`
Ok(7)
Err(ParseIntError { kind: InvalidDigit })결과가 같다. a.parse()? 는 "Ok 면 안의 값을 꺼내라. Err 면 그 Err 를 돌려주며 함수를 지금 끝내라" 는 뜻이다. 위의 match 와 똑같은 일을 한다. 두 번째 호출에서는 "four" 의 parse 가 실패해서, y 줄에서 함수가 끝나고 Err 가 돌아왔다.
03 ? 는 Result 를 돌려주는 함수에서만
보통의 main 에서 ? 를 써 보자.
fn main() {
let x: i32 = "42".parse()?;
println!("{x}");
}error[E0277]: the `?` operator can only be used in a function that returns `Result` or `Option` (or another type that implements `FromResidual`)
--> src/main.rs:2:30
|
1 | fn main() {
| --------- this function should return `Result` or `Option` to accept `?`
2 | let x: i32 = "42".parse()?;
| ^ cannot use the `?` operator in a function that returns `()`
|
help: consider adding return type
|
1 ~ fn main() -> Result<(), Box<dyn std::error::Error>> {
2 | let x: i32 = "42".parse()?;
3 | println!("{x}");
4 + Ok(())- 무엇 —
?는Result나Option을 돌려주는 함수에서만 쓸 수 있다. - 어디 — 2번째 줄의
?. 1번째 줄의fn main()도 짚는다. 이 함수는()(값 없음, 11편)를 돌려준다. - 고치는 법 —
help:가main에 반환 타입을 달고 끝에Ok(())를 넣으라고 한다.~줄이 바뀐 줄,+줄이 더한 줄이다.
? 는 실패하면 Err 를 돌려주는 것이라, 돌려줄 곳이 Result 여야 한다. help 가 쓴 Box<dyn std::error::Error> 는 "어떤 오류든" 이라는 타입인데, 33편의 트레이트를 배운 뒤에 읽으면 된다. 지금은 오류가 하나뿐이니 ParseIntError 를 그대로 쓴다.
04 실습 — main 이 Result 를 돌려주면
use std::num::ParseIntError;
fn main() -> Result<(), ParseIntError> {
let x: i32 = "42".parse()?;
println!("x = {x}");
let y: i32 = "forty".parse()?;
println!("y = {y}");
Ok(())
} Compiling total v0.1.0 (~/projects/total)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.16s
Running `target/debug/total`
x = 42
Error: ParseIntError { kind: InvalidDigit }fn main() -> Result<(), ParseIntError>는 "성공하면 값 없음, 실패하면ParseIntError" 다. 끝의Ok(())가 "잘 끝났다" 다."42"는 성공해서x = 42를 찍었다."forty"에서?가Err를 돌려주며main이 끝났다. Rust 가 그 오류를Error: …로 찍고, 프로그램은 실패를 뜻하는 종료 코드 1 로 끝난다. 패닉(종료 코드 101)과 다르게, 정해진 방법으로 실패를 알린 것이다.
셸에서 cargo run; echo $? 로 돌리면 마지막에 종료 코드가 찍힌다. 다른 프로그램이나 스크립트가 이 숫자를 보고 성공·실패를 안다. 34편의 실습 프로그램이 이 방식으로 실패를 알린다. 다음 편은 같은 코드를 여러 타입에 쓰는 제네릭이다.