티스토리 뷰
Control flow
- if
- If 다음에는 bool 타입이 와야 한다.
fn main() {
let number = 6;
if number % 4 == 0 {
println!("number is divisible by 4");
} else if number % 3 == 0 {
println!("number is divisible by 3");
} else if number % 2 == 0 {
println!("number is divisible by 2");
} else {
println!("number is not divisible by 4, 3, or 2");
}
}
// if 문을 식(expression)으로 사용할 수 있다.
let condition = true;
let y = if condition { 5 } else { 6 };
println!("y는 {y}입니다"); // y는 입니다
- loop
- loop는 값을 리턴할 수 있다.
// 반복이라는 글자를 계속 출력한다.
loop {
println!("반복!");
}
// counter 가 3이되면 loop를 종료시킨다.
let mut counter = 0;
loop {
println!("반복");
counter += 1;
if counter == 3{
break;
}
}
// loop 의 마지막을 반환값으로 줄 수 있다.
let mut counter = 0;
let x = loop {
println!("반복");
counter += 1;
if counter == 3{
break counter;
}
}
println!("x는 {x}입니다."); // x는 3입니다.
- while
let mut counter = 0;
while (counter < 3) {
println!("반복!");
counter += 1;
}
// 배열의 모든 원소 순회
let xs [1,2,3,4,5];
let mut idx = 0;
while (idx < xs.len()) {
println!("xs[{}] {}", idx, xs[idx]);
idx+=1;
}
- for
// for each 방식 전체 순회
let xs [1,2,3,4,5];
for x in xs {
println!("x = {}", x);
}
// 배열 요소를 접근해서 전체 순회
for i in (0..5) {
println!("xs = {}", xs[i]);
}
// 거꾸로 순회한다.
for i in (0..5).rev() {
println!("xs = {}", xs[i]);
}
if let 문법
if let 구문을 사용해서 나머지 패턴을 무시하고 하나의 패턴과 일치하는 값을 처리할 수 있다. 아래는 config_max 변수가 Some(..) 이면 .. 을 출력하고, 그렇지 않으면 무시하는 코드다.
fn main() {
let config_max = Some(3u8);
match config_max {
Some(max) => println!("the maximum is configured to be {max}"),
_ => (),
}
}
위 코드는 아래와 같이 if let 문법을 사용하여 간단하게 나타낼 수 있다.
fn main() {
let config_max = Some(3);
if let Some(max) = config_max {
println!("the maximum is configured to be {max}");
}
// else {
} // 를 작성해서 _ 에 해당하는 코드가 있으면 그것도 작성할 수 있다.
}
에러 처리
- 복구 가능한 에러
- ex ) File not found → 다른 경로를 입력받아서 재시도
- 프로그래머가 예외처리를 해놓을 수 있다.
- Result <T, E>
- 복구 불가능한 에러
- ex ) out of index
- 프로그래머가 예외처리를 할 수 없거나 하면 안된다. 에러가 발생하면 바로 시스템이 다운되야 한다.
- panic!
복구 불가능한 오류에는 panic!
- panic! 매크로를 사용하여 강제 종료를 시킬 수 있다.
fn main() {
panic!("강제종료");
}
fn main() {
let arr = [1, 2, 3];
arr[99]; // error: this operation will panic at runtime
}
에러 처리에는 Result<T, E>
아래 코드는 hello.txt 파일이 있으면 파일을 반환하고 없으면 panic! 을 일으켜서 프로그램을 종료한다.
// enum Result<T, E> {
// Ok(T),
// Err(E),
// }
use std::fs::File;
fn main() {
let greeting_file_result = File::open("hello.txt");
let greeting_file = match greeting_file_result {
Ok(file) => file,
Err(error) => panic!("Problem opening the file: {error:?}"), // ("{:?}", error)의 다른 표현
};
}
아래 코드는 파일이 있으면 Ok(f) 를 반환 하고, 없으면 에러를 반환한다. match 를 이용해서 에러의 종류를 확인하고 에러 종류가 NotFound 라면 파일을 생성한다. 그 외 문제라면 패닉을 발생시켜 프로그램을 종료한다. (13장에서 closure 를 배우고 나면 아래 코드에 나오는 파일 생성에 대한 실패 분기를 축약할 수 있다)
use std::{fs::File, io::ErrorKind};
fn main() {
let file_result = File::open("hello.txt");
let file = match file_result {
Ok(f) => f,
Err(e) => match e.kind() {
ErrorKind::NotFound => match File::create("hello.txt") {
Ok(fc) => fc,
Err(e) => panic!("파일 생성 실패!: {e:?}"),
},
_ => panic!("파일 접근 실패: {:?}", e),
},
};
}
항상 위와 같이 쓰는 건 귀찮으니까 러스트에서 제공하는 unwrap() 함수로 대체할 수 있다. 정상적으로 열었을 때는 file을 돌려주지만 실패 했을 때는 panic 을 일으킨다.
fn main() {
let file = File::open("hello.txt").unwrap();
}
조금 더 디테일한 커스텀을 원한다면 expect() 를 사용할 수 있다. expect 를 이용하면 panic 이 발생했을때 어떤 메시지를 출력할 것인지 정의할 수 있다.
fn main() {
let file = File::open("hello.txt").expect("파일을 열 수 없음");
}
에러 전파하기
- 내가 직접 에러를 처리하지 않고 호출한 쪽에 에러 처리를 위임한다. ( Java 의 Throw 개념 )
read_username_from_file() 을 호출하는 곳에서 에러처리를 하게 작성한 코드.
use std::fs::File;
use std::io::{self, Error, Read};
fn read_username_from_file() -> Result<String, Error> {
let file_result = File::open("hello.txt");
let mut file = match file_result {
Ok(file) => file,
Err(e) => return Err(e),
};
let mut username = String::new();
match file.read_to_string(&mut username) {
Ok(_) => Ok(username),
Err(e) => Err(e),
}
}
fn main() {
let username = read_username_from_file();
println!("{:?}", username);
let username = username.unwrap();
println!("{username}");
}
/*
Ok("pott\\n")
pott
*/
hello.txt 파일 이 있으면 파일에서 내용을 읽어 username(=pott) 을 반환하고 , 없으면 Error 를 던진다.
위와 같은 형식은 너무나 빈번하게 사용하기 때문에 아래와 같이 ? 로 축약해서 작성할 수 있다.
fn read_username_short() -> Result<String, Error> {
let mut file = File::open("hello.txt")?;
let mut username = String::new();
file.read_to_string(&mut username)?;
Ok(username)
}
위의 표현은 아래로 더 축약할 수 있다.
/* ?축약표현을 연이어 쓸 수도 있습니다. */
fn read_username_shorter() -> Result<String, Error> {
let mut username = String::new();
File::open("hello.txt")?.read_to_string(&mut username)?;
Ok(username)
}
최종적으로 다음과 같이 한줄로 축약해서 사용할 수 있다.
use std::fs;
/* fs::read_to_string은 내부적으로 ?축약표현이 이미 들어있습니다. */
fn read_username_even_shorter() -> Result<String, Error> {
fs::read_to_string("hello.txt")
}
Result가 반환되는 함수에서 ?연산자를 사용할 수 있으며, Option이 반환되는 함수에서 ? 연산자를 사용할 수 있지만, 두 가지를 혼합할 수는 없다. ? 연산자는 Result를 Option으로 자동 변환하거나 그 반대로 작동하지 않습니다. 그런 경우 Result에서 ok 메서드나 Option에서 ok_or 메서드와 같은 메서드를 사용하여 명시적으로 변환할 수 있다.
아래 코드는 에러가 난다. 즉, ? 는 반환 타입이 Result<T,E> 거나 Option<T> 인 함수에서만 사용가능 하다.
use std::fs::File;
fn main() {
let greeting_file = File::open("hello.txt")?;
}
에러가 안나게 수정해 보았다.
use std::fs::File;
use std::io::Error;
fn main() {
let file = open(); // file 의 타입은 Result<File, Error>
println!("{:?}", file);
}
fn open() -> Result<File, Error> {
let greeting_file = File::open("hello.txt")?;
Ok(greeting_file)
}
공식문서에서는 다음과 같이 더 직관적인 방법을 제공한다.
use std::error::Error;
use std::fs::File;
fn main() -> Result<(), Box<dyn Error>> {
let greeting_file = File::open("hello.txt")?;
Ok(())
}
일단 main() 은 조금 특별한 함수라서 Result<File, Error> 로 할 수는 없고, Result<(), Box<dyn Error>> 로 반환해야 한다.
Box<dyn Error> 유형은 트레잇 객체로, 일단은 어떤 종류의 오류 로 읽으면 된다. Box<dyn Error> 은 어떤 Err 값도 조기에 반환될 수 있게 한다. 이 main 함수의 본체가 std::io::Error 유형의 오류만 반환하더라도, Box<dyn Error>를 지정함으로써, 다른 오류를 반환하는 코드가 greeting_file 변수 선언 앞뒤로 추가 되더라도 이 시그니처는 계속해서 사용할 수 있다.