안동민 개발노트 아이콘

안동민 개발노트

11장 : I/O 프로젝트: 커맨드 라인 프로그램 만들기

파일 읽기

이제는 file_path 인수에 명시된 파일을 읽는 기능을 추가해보겠습니다. 우선 테스트에 사용할 샘플 파일이 필요합니다. 여러 줄의 몇 개의 반복된 단어들로 구성된 작은 양의 텍스트로 된 파일을 사용하겠습니다. 예제 11-3은 딱 맞게 사용될 에밀리 딕킨슨(Emily Dickinson)의 시가 있습니다! 프로젝트의 루트 레벨에 poem.txt이라는 이름의 파일을 만들고, 시 ‘I’m Nobody! Who are you?’를 입력하세요.

예제 11-3: 에밀리 딕킨슨의 시는 좋은 테스트 케이스를 만들어 줍니다
poem.txt
I'm nobody! Who are you?
Are you nobody, too?
Then there's a pair of us - don't tell!
They'd banish us, you know.

How dreary to be somebody!
How public, like a frog
To tell your name the livelong day
To an admiring bog!

텍스트를 채워 넣었다면 예제 11-4처럼 src/main.rs에 파일을 읽는 코드를 추가하세요.

예제 11-4: 두 번째 인수로 명시된 파일의 내용물 읽기
src/main.rs
use std::env;
use std::fs;

fn main() {
    // --생략--
    println!("In file {}", file_path);

    let contents = fs::read_to_string(file_path)
        .expect("Should have been able to read the file");

    println!("With text:\n{contents}");
}

먼저 use 구문을 사용하여 표준 라이브러리의 연관된 부분을 가져옵니다.

아래 다이어그램은 이 절의 핵심 흐름을 역할과 상태 전환 중심으로 정리한 것입니다.

파일을 다루기 위해서는 std::fs가 필요하죠.

main에서 새로운 구문 fs::read_to_stringfile_path를 받아서 그 파일을 열고, 파일 내용물의 std::io::Result<String>을 반환합니다.

그다음 다시 한번 임시로 println! 구문을 추가하여 파일을 읽은 후 contents의 값을 출력하는 것으로 현재까지의 프로그램이 잘 작동하는지 확인합니다.

지금까지의 흐름은 파일 경로를 받아 문자열을 얻고, 잠시 expect와 출력으로 결과를 확인하는 데 초점이 있습니다. 다만 바로 다음 단계에서는 이 흐름을 관심사별로 나누고, 실패했을 때의 처리를 더 차분하게 다듬어야 합니다.

첫 번째 커맨드 라인 인수에는 아무 문자열이나 넣고(아직 검색 부분은 구현하지 않았으므로) 두 번째 인수에는 poem.txt 파일을 넣어서 이 코드를 실행해봅시다.

$ cargo run -- the poem.txt
   Compiling minigrep v0.1.0 (file:///projects/minigrep)
    Finished dev [unoptimized + debuginfo] target(s) in 0.0s
     Running `target/debug/minigrep the poem.txt`
Searching for the
In file poem.txt
With text.
I'm nobody! Who are you?
Are you nobody, too?
Then there's a pair of us - don't tell!
They'd banish us, you know.

How dreary to be somebody!
How public, like a frog
To tell your name the livelong day
To an admiring bog!

훌륭해요! 코드가 파일의 내용물을 읽은 뒤 출력했습니다. 하지만 이 코드에는 몇 가지 결점이 있습니다. 현재 main 함수에는 여러 가지 기능이 있습니다. 일반적으로 함수 하나당 단 하나의 아이디어에 대한 기능을 구현할 때 함수가 더 명료해지고 관리하기 쉬워집니다. 또 한 가지 문제는 처리 가능한 수준의 에러 처리를 안 하고 있다는 점입니다. 프로그램은 아직 작고, 따라서 이러한 결점이 큰 문제는 아니지만, 프로그램이 커지면 이 문제들을 깔끔하게 고치기 어려워질 것입니다. 작은 양의 코드를 리팩터링하는 것이 훨씬 쉽기 때문에, 프로그램을 개발할 때 일찍 리팩터링하는 것은 좋은 관행입니다. 이걸 바로 다음에 하겠습니다.

아래 다이어그램은 파일 읽기 단계가 성공한 뒤에도 남아 있는 책임 분리와 오류 처리 문제를 다음 리팩터링 항목으로 정리한 것입니다.

파일 읽기 오류 처리 경로

파일 읽기 코드는 경로 입력, 열기 실패, 읽기 실패, 문자열 변환을 나누어 다뤄야 원인 메시지가 분명해집니다.