Mastering Rust의 Result Enum을 위한 오류 처리
Ethan Miller
Product Engineer · Leapcell

Rust의 Result 타입
Rust는 독특한 오류 처리 메커니즘을 제공하는 시스템 프로그래밍 언어입니다. Rust에서 오류는 복구 가능한 오류와 복구 불가능한 오류의 두 가지 유형으로 분류됩니다. 복구 가능한 오류의 경우 Rust는 이를 처리하기 위해 Result 타입을 제공합니다.
Result 타입의 정의
Result 타입은 Ok와 Err 두 가지 variant를 가진 열거형입니다. Ok variant는 성공적인 작업을 나타내며 성공 값을 포함하고, Err variant는 실패한 작업을 나타내며 오류 값을 포함합니다.
아래는 Result 타입의 정의입니다.
enum Result<T, E> { Ok(T), Err(E), }
여기서 T는 성공 값의 타입을 나타내고, E는 오류 값의 타입을 나타냅니다.
Result 타입의 용도
Result 타입은 함수 반환 값으로 흔히 사용됩니다. 함수가 성공적으로 실행되면 Ok variant를 반환하고, 실패하면 Err variant를 반환합니다.
아래는 간단한 예제입니다.
fn divide(numerator: f64, denominator: f64) -> Result<f64, String> { if denominator == 0.0 { Err("0으로 나눌 수 없습니다".to_string()) } else { Ok(numerator / denominator) } } fn main() { let result = divide(4.0, 2.0); match result { Ok(value) => println!("결과: {}", value), Err(e) => println!("오류: {}", e), } }
이 예제에서 divide 함수는 분자와 분모 두 개의 인수를 받습니다. 분모가 0이면 Err variant를 반환하고, 그렇지 않으면 Ok variant를 반환합니다.
main 함수에서는 divide 함수를 호출하고 match 구문을 사용하여 반환 값을 처리합니다. 반환 값이 Ok이면 결과를 출력하고, Err이면 오류 메시지를 출력합니다.
Result로 오류를 처리하는 방법
Result 타입을 반환하는 함수를 호출할 때 잠재적인 오류를 처리해야 합니다. 이를 수행하는 방법에는 여러 가지가 있습니다.
match 구문 사용
match 구문은 Rust에서 Result 타입 오류를 처리하는 가장 일반적인 방법입니다. 반환 값에 따라 다른 작업을 실행할 수 있습니다.
다음은 간단한 예입니다.
fn divide(numerator: f64, denominator: f64) -> Result<f64, String> { if denominator == 0.0 { Err("0으로 나눌 수 없습니다".to_string()) } else { Ok(numerator / denominator) } } fn main() { let result = divide(4.0, 2.0); match result { Ok(value) => println!("결과: {}", value), Err(e) => println!("오류: {}", e), } }
이 예제에서는 match 구문을 사용하여 divide 함수의 반환 값을 처리합니다. Ok를 반환하면 결과를 출력하고, Err를 반환하면 오류 메시지를 출력합니다.
if let 구문 사용
if let 구문은 match의 단순화된 버전입니다. 하나의 case만 일치시킬 수 있으며 다른 case를 처리할 필요가 없습니다. if let 구문은 Result 타입의 한 가지 case만 신경 쓸 때 자주 사용됩니다.
다음은 간단한 예입니다.
fn divide(numerator: f64, denominator: f64) -> Result<f64, String> { if denominator == 0.0 { Err("0으로 나눌 수 없습니다".to_string()) } else { Ok(numerator / denominator) } } fn main() { let result = divide(4.0, 2.0); if let Ok(value) = result { println!("결과: {}", value); } }
이 예제에서는 if let 구문을 사용하여 divide 함수의 반환 값을 처리합니다. Ok를 반환하면 결과를 출력하고, 그렇지 않으면 아무 일도 일어나지 않습니다.
? 연산자 사용
? 연산자는 함수 내에서 오류를 편리하게 전파할 수 있는 Rust의 특별한 구문입니다. Result 타입을 반환하는 함수를 호출할 때 ? 연산자는 오류 처리를 단순화할 수 있습니다.
다음은 간단한 예입니다.
fn divide(numerator: f64, denominator: f64) -> Result<f64, String> { if denominator == 0.0 { Err("0으로 나눌 수 없습니다".to_string()) } else { Ok(numerator / denominator) } } fn calculate(numerator: f64, denominator: f64) -> Result<f64, String> { let result = divide(numerator, denominator)?; Ok(result * 2.0) } fn main() { let result = calculate(4.0, 2.0); match result { Ok(value) => println!("결과: {}", value), Err(e) => println!("오류: {}", e), } }
이 예제에서 calculate 함수는 내부적으로 divide 함수를 호출하고 ? 연산자를 사용하여 오류 처리를 단순화합니다. divide가 Err를 반환하면 calculate는 즉시 Err를 반환하고, 그렇지 않으면 실행이 계속됩니다.
Result의 일반적인 메서드
Result 타입은 오류 처리를 더 편리하게 만드는 여러 유용한 메서드를 제공합니다.
is_ok 및 is_err 메서드
is_ok 및 is_err 메서드는 Result가 각각 Ok 또는 Err variant인지 확인합니다.
다음은 간단한 예입니다.
fn divide(numerator: f64, denominator: f64) -> Result<f64, String> { if denominator == 0.0 { Err("0으로 나눌 수 없습니다".to_string()) } else { Ok(numerator / denominator) } } fn main() { let result = divide(4.0, 2.0); if result.is_ok() { println!("결과: {}", result.unwrap()); } else { println!("오류: {}", result.unwrap_err()); } }
이 예제에서는 is_ok 메서드를 사용하여 divide의 반환 값이 Ok인지 확인합니다. 그렇다면 unwrap을 사용하여 성공 값을 가져와서 출력하고, 그렇지 않으면 unwrap_err을 사용하여 오류 메시지를 가져와서 출력합니다.
unwrap 및 unwrap_err 메서드
unwrap 및 unwrap_err 메서드는 Result에서 각각 성공 또는 오류 값을 검색합니다. Result가 예상한 variant가 아니면 패닉이 발생합니다.
다음은 간단한 예입니다.
fn divide(numerator: f64, denominator: f64) -> Result<f64, String> { if denominator == 0.0 { Err("0으로 나눌 수 없습니다".to_string()) } else { Ok(numerator / denominator) } } fn main() { let result = divide(4.0, 2.0); let value = result.unwrap(); println!("결과: {}", value); }
이 예제에서는 unwrap을 사용하여 divide 함수의 성공 값을 가져옵니다. 반환 값이 Ok가 아니면 패닉이 발생합니다.
expect 및 expect_err 메서드
expect 및 expect_err 메서드는 unwrap 및 unwrap_err과 유사하지만 사용자 정의 오류 메시지를 지정할 수 있습니다. Result가 예상한 variant가 아니면 패닉이 발생하고 지정된 메시지가 출력됩니다.
다음은 간단한 예입니다.
fn divide(numerator: f64, denominator: f64) -> Result<f64, String> { if denominator == 0.0 { Err("0으로 나눌 수 없습니다".to_string()) } else { Ok(numerator / denominator) } } fn main() { let result = divide(4.0, 2.0); let value = result.expect("나누기 실패"); println!("결과: {}", value); }
이 예제에서는 expect를 사용하여 divide 함수의 성공 값을 검색합니다. 반환 값이 Ok가 아니면 패닉이 발생하고 지정된 오류 메시지가 출력됩니다.
Result의 특징 및 장점
Result 타입은 다음과 같은 특징과 장점을 가지고 있습니다.
- 명시적인 오류 처리:
Result타입은 프로그래머가 오류를 명시적으로 처리하도록 강제하여 오류가 무시되거나 간과되는 것을 방지합니다. - 타입 안전성:
Result타입은 성공 또는 오류 값의 모든 타입을 담을 수 있는 제네릭 타입으로, 타입 안전성을 보장하고 타입 변환 오류를 방지합니다. - 편리한 오류 전파: Rust는 함수에서 오류를 쉽게 전파할 수 있도록
?연산자를 제공합니다. - 쉬운 구성:
Result타입은and,or,and_then및or_else와 같은 다양한 구성 메서드를 제공하여 여러Result값을 더 쉽게 결합할 수 있습니다.
실제 코드에서 Result 사용
실제 코드에서는 사용자 정의 오류 타입을 정의하고 Result 타입을 사용하여 오류 정보를 반환하는 경우가 많습니다.
다음은 간단한 예입니다.
use std::num::ParseIntError; type Result<T> = std::result::Result<T, MyError>; #[derive(Debug)] enum MyError { DivideByZero, ParseIntError(ParseIntError), } impl From<ParseIntError> for MyError { fn from(e: ParseIntError) -> Self { MyError::ParseIntError(e) } } fn divide(numerator: &str, denominator: &str) -> Result<f64> { let numerator: f64 = numerator.parse()?; let denominator: f64 = denominator.parse()?; if denominator == 0.0 { Err(MyError::DivideByZero) } else { Ok(numerator / denominator) } } fn main() { let result = divide("4", "2"); match result { Ok(value) => println!("결과: {}", value), Err(e) => println!("오류: {:?}", e), } }
이 예제에서:
DivideByZero및ParseIntError의 두 가지 variant를 포함하는 사용자 정의 오류 타입MyError를 정의합니다.MyError를 오류 타입으로 설정하여 타입 별칭Result를 정의합니다.divide함수는 두 개의 문자열 인수를 취하고 이를f64로 파싱하려고 시도합니다. 파싱에 실패하면?연산자가 오류를 전파합니다. 분모가0이면Errvariant가 반환되고, 그렇지 않으면 함수는Ok를 반환합니다.
main 함수에서는 divide를 호출하고 match 구문을 사용하여 반환 값을 처리합니다. Ok를 반환하면 결과를 출력하고, Err를 반환하면 오류 메시지를 출력합니다.
Result로 파일 읽기/쓰기 오류 처리
파일 작업 시 파일이 없거나 권한이 부족한 경우와 같은 다양한 오류가 발생할 수 있습니다. 이러한 오류는 Result 타입을 사용하여 처리할 수 있습니다.
다음은 간단한 예입니다.
use std::fs; use std::io; fn read_file(path: &str) -> Result<String, io::Error> { fs::read_to_string(path) } fn main() { let result = read_file("test.txt"); match result { Ok(content) => println!("파일 내용: {}", content), Err(e) => println!("오류: {}", e), } }
이 예제에서:
read_file함수는 파일 경로를 인수로 취하고fs::read_to_string을 사용하여 파일 내용을 읽습니다.fs::read_to_string은 파일 내용을 포함하는 성공 값과io::Error타입의 오류 값을 가진Result타입을 반환합니다.main에서read_file을 호출하고match를 사용하여 반환 값을 처리합니다.Ok를 반환하면 파일 내용이 출력되고,Err를 반환하면 오류 메시지가 출력됩니다.
Result로 네트워크 요청 오류 처리
네트워크 요청을 수행할 때 연결 시간 초과 또는 서버 오류와 같은 다양한 오류가 발생할 수 있습니다. 이러한 오류도 Result 타입을 사용하여 처리할 수 있습니다.
다음은 간단한 예입니다.
use std::io; use std::net::TcpStream; fn connect(host: &str) -> Result<TcpStream, io::Error> { TcpStream::connect(host) } fn main() { let result = connect("example.com:80"); match result { Ok(stream) => println!("{}(으)로 연결됨", stream.peer_addr().unwrap()), Err(e) => println!("오류: {}", e), } }
이 예제에서:
connect함수는 호스트 주소를 인수로 취하고TcpStream::connect를 사용하여 TCP 연결을 설정합니다.TcpStream::connect는TcpStream타입의 성공 값과io::Error타입의 오류 값을 가진Result타입을 반환합니다.main에서connect를 호출하고match를 사용하여 반환 값을 처리합니다.Ok를 반환하면 연결 정보가 출력되고,Err를 반환하면 오류 메시지가 출력됩니다.
Result 및 오류 처리에 대한 모범 사례
Result로 오류를 처리할 때 다음 모범 사례는 더 나은 코드를 작성하는 데 도움이 될 수 있습니다.
- 사용자 정의 오류 타입 정의: 사용자 정의 오류 타입은 오류 정보를 보다 효과적으로 구성하고 관리하는 데 도움이 됩니다.
?연산자를 사용하여 오류 전파:?연산자를 사용하면 함수에서 오류를 쉽게 전파할 수 있습니다.unwrap및expect의 과도한 사용 방지: 이러한 메서드는Errvariant가 발생하면 패닉을 유발합니다. 대신match또는if let을 사용하여 오류를 적절하게 처리하십시오.- 구성 메서드를 사용하여 여러
Result값 결합:and,or,and_then및or_else와 같은 메서드는 여러Result값을 효율적으로 결합하는 데 도움이 됩니다.
Rust 프로젝트 호스팅을 위한 최고의 선택, Leapcell입니다.
Leapcell은 웹 호스팅, 비동기 작업 및 Redis를 위한 차세대 서버리스 플랫폼입니다.
다국어 지원
- Node.js, Python, Go 또는 Rust로 개발하십시오.
무제한 프로젝트를 무료로 배포
- 사용량에 대해서만 비용을 지불하십시오. 요청도 없고 요금도 없습니다.
타의 추종을 불허하는 비용 효율성
- 유휴 요금 없이 사용한 만큼만 지불하십시오.
- 예: $25는 평균 응답 시간 60ms에서 694만 건의 요청을 지원합니다.
간소화된 개발자 경험
- 손쉬운 설정을 위한 직관적인 UI.
- 완전 자동화된 CI/CD 파이프라인 및 GitOps 통합.
- 실행 가능한 통찰력을 위한 실시간 메트릭 및 로깅.
손쉬운 확장성과 고성능
- 고도의 동시성을 쉽게 처리할 수 있도록 자동 확장됩니다.
- 운영 오버헤드가 전혀 없으므로 구축에만 집중하십시오.
문서에서 자세히 알아보십시오!
X에서 팔로우하세요: @LeapcellHQ

