파일 읽기
명령줄에서 받은 경로의 텍스트 파일을 읽어 출력하고 존재하지 않거나 읽을 수 없는 파일의 실패 경로를 확인합니다.
이제는 file_path 인수에 명시된 파일을 읽는 기능을 추가해보겠습니다.
우선 테스트에 사용할 샘플 파일이 필요합니다.
몇 개의 단어가 여러 줄에 걸쳐 반복되는 짧은 텍스트 파일을 사용하겠습니다.
예제 11-3에는 이 용도에 알맞은 에밀리 딕킨슨(Emily Dickinson)의 시가 있습니다!
프로젝트의 루트 레벨에 poem.txt라는 이름의 파일을 만들고, 시 ‘I’m Nobody! Who are you?’를 입력하세요.
예제 11-3: 에밀리 딕킨슨의 시는 좋은 테스트 케이스를 만들어 줍니다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에 파일을 읽는 코드를 추가하세요.
파일 경로에서 UTF-8 문자열까지의 Result 경계
poem.txt 전체 UTF-8 읽기→Ok contents 또는 io Error→본문 expect 패닉이라는 요청/결과/호출자 정책이다.
RUST · MINIGREP
파일 읽기의 Result와 호출자의 오류 정책
경로에서 전체 UTF-8 텍스트를 읽는 함수의 결과와, 이 절에서 expect로 그 결과를 처리하는 방법을 구분합니다.
읽기 호출
fs::read_to_string("poem.txt") → io::Result<String>
성공: Ok(String)
파일 전체의 UTF-8 텍스트를 소유한 String으로 반환하고 본문은 contents에 저장합니다.
실패: Err(io::Error)
열기·읽기 실패나 유효하지 않은 UTF-8이면 Err입니다. 이 절의 expect는 문맥과 오류를 담아 패닉을 발생시킵니다.
현재 코드
fs::read_to_string(file_path)
.expect("Should have been able \
to read the file");실패 처리 방침을 호출하는 쪽에서 정합니다. 11장 3절은 ?로 오류를 돌려줍니다.
파일 경계
임의 바이트에는 fs::read를 사용합니다. 별도의 경로 사전 확인도 나중의 읽기 성공을 보장하지 않습니다. 전체 읽기의 메모리 사용량은 파일 크기에 따라 늘어납니다.
운영체제별 오류 종류와 문구는 달라질 수 있습니다. expect는 고정 문맥뿐 아니라 오류의 Debug 표현도 포함합니다.
이 예의 조건
- 본문과 원 도표에 제시된 사례의 입력·조건을 사용합니다. 설명용 수치나 독립 비교 경로는 각 단계에 표시합니다.
- 현재 상태·값과 전환 근거의 위치는 모든 단계에서 같습니다. 조건이 맞지 않거나 실행되지 않은 경로를 성공한 동작으로 해석하지 않습니다.
관계도의 보충 설명
| 범위 | 원문 |
|---|---|
| 관계·조건 설명 | 세 노드와 두 분기다. 파일 전체 읽기의 성공은 Ok의 String이고 실패는 io Error다. 왼쪽 성공 레일은 Ok로, 오른쪽 실패 레일은 Err로 직접 간다. 둘을 순서대로 거치는 것이 아니다. 빈 UTF8 파일은 빈 String으로 성공하며 현재 expect는 Err를 패닉으로 바꾼다. 검색 일치 여부는 아직 검사하지 않는다. |
| 관찰·상황 | 값·보충 정보 | 변화·이유 |
|---|---|---|
| 파일 요청 poem.txt 전체를 UTF-8 String으로 요청한다. |
|
|
| Result 반환 성공 contents와 io Error를 나눈다. |
|
|
| 본문 정책 이 예의 expect가 실패 시 패닉한다. |
|
|
파일 경로에서 UTF-8 문자열까지의 Result 경계
poem.txt 전체 UTF-8 읽기→Ok contents 또는 io Error→본문 expect 패닉이라는 요청/결과/호출자 정책이다.
RUST · MINIGREP
파일 읽기의 Result와 호출자의 오류 정책
경로에서 전체 UTF-8 텍스트를 읽는 함수의 결과와, 이 절에서 expect로 그 결과를 처리하는 방법을 구분합니다.
읽기 호출
fs::read_to_string("poem.txt") → io::Result<String>
성공: Ok(String)
파일 전체의 UTF-8 텍스트를 소유한 String으로 반환하고 본문은 contents에 저장합니다.
실패: Err(io::Error)
열기·읽기 실패나 유효하지 않은 UTF-8이면 Err입니다. 이 절의 expect는 문맥과 오류를 담아 패닉을 발생시킵니다.
현재 코드
fs::read_to_string(file_path)
.expect("Should have been able \
to read the file");실패 처리 방침을 호출하는 쪽에서 정합니다. 11장 3절은 ?로 오류를 돌려줍니다.
파일 경계
임의 바이트에는 fs::read를 사용합니다. 별도의 경로 사전 확인도 나중의 읽기 성공을 보장하지 않습니다. 전체 읽기의 메모리 사용량은 파일 크기에 따라 늘어납니다.
운영체제별 오류 종류와 문구는 달라질 수 있습니다. expect는 고정 문맥뿐 아니라 오류의 Debug 표현도 포함합니다.
이 예의 조건
- 본문과 원 도표에 제시된 사례의 입력·조건을 사용합니다. 설명용 수치나 독립 비교 경로는 각 단계에 표시합니다.
- 현재 상태·값과 전환 근거의 위치는 모든 단계에서 같습니다. 조건이 맞지 않거나 실행되지 않은 경로를 성공한 동작으로 해석하지 않습니다.
관계도의 보충 설명
| 범위 | 원문 |
|---|---|
| 관계·조건 설명 | 세 노드와 두 분기다. 파일 전체 읽기의 성공은 Ok의 String이고 실패는 io Error다. 왼쪽 성공 레일은 Ok로, 오른쪽 실패 레일은 Err로 직접 간다. 둘을 순서대로 거치는 것이 아니다. 빈 UTF8 파일은 빈 String으로 성공하며 현재 expect는 Err를 패닉으로 바꾼다. 검색 일치 여부는 아직 검사하지 않는다. |
| 관찰·상황 | 값·보충 정보 | 변화·이유 |
|---|---|---|
| 파일 요청 poem.txt 전체를 UTF-8 String으로 요청한다. |
|
|
| Result 반환 성공 contents와 io Error를 나눈다. |
|
|
| 본문 정책 이 예의 expect가 실패 시 패닉한다. |
|
|
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_string이 file_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 함수에는 여러 가지 기능이 있습니다.
일반적으로 함수 하나당 단 하나의 아이디어에 대한 기능을 구현할 때 함수가 더 명료해지고 관리하기 쉬워집니다.
또 한 가지 문제는 처리 가능한 수준의 에러 처리를 안 하고 있다는 점입니다.
프로그램은 아직 작고, 따라서 이러한 결점이 큰 문제는 아니지만, 프로그램이 커지면 이 문제들을 깔끔하게 고치기 어려워질 것입니다.
작은 양의 코드를 리팩터링하는 것이 훨씬 쉽기 때문에, 프로그램을 개발할 때 일찍 리팩터링하는 것은 좋은 관행입니다.
이걸 바로 다음에 하겠습니다.
앞의 // --생략--에는 예제 11-2의 인수 수집과 query·file_path 선언이 들어갑니다. 그 발췌만 새 main.rs에 붙이면 경로 변수가 없어 컴파일되지 않습니다. 이 단계는 파일 내용을 전부 읽어 보여줄 뿐 아직 검색어로 줄을 고르지 않습니다.
대표 출력의 With text.는 보존한 출력 예시의 표기이며, 현재 println!("With text:\n{contents}")가 만드는 첫 줄은 콜론이 있는 With text:입니다. 직접 대조할 때는 코드의 문자열을 기준으로 보세요.
예시
현재 작업 디렉터리의 sample.txt가 UTF-8 텍스트 one\ntwo\n를 담는다고 합시다. 파일 읽기가 성공하면 contents는 줄바꿈까지 포함한 하나의 String입니다. 마지막 줄바꿈이 있다고 해서 파일이 두 번 읽히거나 검색 결과가 생기는 것은 아닙니다. 상대 경로 sample.txt는 실행 중인 프로세스의 작업 디렉터리를 기준으로 찾으며, 소스의 src 폴더를 자동 기준으로 삼지 않습니다.
read_to_string은 비어 있는 UTF-8 파일에는 빈 문자열을 반환할 수 있습니다. 경로가 없거나 읽을 수 없거나 내용이 유효한 UTF-8이 아니면 Err가 되고, 지금 붙인 expect는 그 오류에서 패닉을 일으킵니다. 실패를 Result 값으로 호출자에게 넘기는 구조는 다음 절에서 만듭니다. 이 계약은 표준 라이브러리의 read_to_string에 따릅니다.
독립 연습: 읽을 수 있는 빈 파일, 존재하지 않는 파일, 읽을 수 있으나 바이트 하나가 0xff인 파일을 각각 이 함수에 전달한다고 가정하세요. 어느 경우에 With text: 출력에 도달하며 contents 값은 무엇인가요? 검색어가 파일에 없다는 경우와 파일 읽기가 실패한 경우도 구별하세요.
읽기 성공과 검색 결과 없음 구별하기
빈 파일은 Ok의 빈 문자열이므로 With text: 출력에 도달합니다. 다른 두 파일은 각각 파일 접근과 UTF-8 변환에서 오류가 되어 expect에서 패닉이 나며 그 출력에는 도달하지 않습니다. 검색어가 없는 정상 텍스트도 읽기 자체는 성공합니다. 이 단계에는 검색 함수가 없으므로 일치 여부로 읽기 성공을 판단하지 않습니다. 실제 오류 종류와 메시지는 운영체제·입력에 따라 달라질 수 있으며 여기서 파일 접근을 실행한 결과를 제시한 것은 아닙니다.
파일 읽기 오류 처리 경로
파일 읽기 코드는 경로 입력, 열기 실패, 읽기 실패, 문자열 변환을 나누어 다뤄야 원인 메시지가 분명해집니다.
파일 읽기 학습 판단 기준표
파일 읽기 마지막 점검은 실제 호출 모양과 실패 처리 경계가 자연스럽게 이어지는지에 맞춥니다.