Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

輸入輸出:stdin、檔案讀寫

本集目標

認識 Rust 的輸入輸出方法,學會讀寫檔案。

概念說明

前面學了怎麼用 Path 表示檔案位置、怎麼處理字串內容,這集來學怎麼實際讀寫檔案。

回顧 stdin

第 1 章我們照抄了這三行來讀取使用者輸入:

fn main() {
    let mut input = String::new();
    std::io::stdin().read_line(&mut input).expect("讀取失敗");
    let name = input.trim();
}

你已經學過 String&mut.expect(),這段應該都看得懂了。唯一沒提過的是 std::io::stdin() 回傳一個 Stdin struct,而 read_line 回傳的是 io::Result<usize>——這是 Result<usize, io::Error> 的型別別名,標準庫裡很多輸入輸出函數都用它。

最簡單的檔案讀寫

fs::read_to_string 的參數型別是 impl AsRef<Path>——第 1 集學的 AsRef 在這裡派上用場。你可以傳 &strString&PathPathBuf 都行,不用手動轉換。

讀整個檔案成字串:

use std::fs;

fn main() {
    let content = fs::read_to_string("hello.txt").expect("讀取失敗");
    println!("{}", content);
}

寫入檔案(檔案不存在會建立,存在會覆蓋):

use std::fs;

fn main() {
    fs::write("output.txt", "Hello, file!").expect("寫入失敗");
}

這兩個函數夠簡單,但 read_to_string 會把整個檔案一次讀進記憶體——如果檔案很大就不適合。

File + BufReader:逐行讀取

use std::fs::File;
use std::io::{BufRead, BufReader};

fn main() {
    let file = File::open("data.txt").expect("開啟失敗");
    let reader = BufReader::new(file);

    for line in reader.lines() {
        let line = line.expect("讀取行失敗");
        println!("{}", line);
    }
}

File::open 開啟檔案,BufReader 包在外面提供緩衝區,.lines() 逐行讀取,每行是一個 io::Result<String>

寫入檔案

use std::fs::File;
use std::io::Write;

fn main() {
    let mut file = File::create("output.txt").expect("建立失敗");
    writeln!(file, "第一行").expect("寫入失敗");
    writeln!(file, "第二行").expect("寫入失敗");
}

File::create 建立(或覆蓋)檔案,writeln!println! 很像,只是輸出目標從螢幕換成檔案。要用 writeln! 需要引入 Write trait

ReadWriteBufRead

標準庫用 trait 抽象化輸入輸出:

  • Read:能讀取 bytes 的東西(FileStdinTcpStream 等)
  • Write:能寫入 bytes 的東西(FileStdoutTcpStream 等)
  • BufRead:帶緩衝的讀取,提供 lines() 等方法。BufReader 可以把任何 Read 變成 BufRead

這就是為什麼很多函數的參數寫成 impl Readimpl Write——不管你傳檔案、stdin 還是網路連線,只要實作了對應的 trait 就能用。

範例程式碼

use std::fs::{self, File};
use std::io::{self, BufRead, BufReader, Write};

fn main() -> io::Result<()> {
    // 寫入檔案
    let mut file = File::create("names.txt")?;
    writeln!(file, "Alice")?;
    writeln!(file, "Bob")?;
    writeln!(file, "Charlie")?;

    // 一次讀取整個檔案
    let all = fs::read_to_string("names.txt")?;
    println!("整個檔案:\n{}", all);

    // 逐行讀取
    let file = File::open("names.txt")?;
    let reader = BufReader::new(file);
    for (i, line) in reader.lines().enumerate() {
        println!("第 {} 行:{}", i + 1, line?);
    }

    Ok(())
}

重點整理

  • io::Result<T>Result<T, io::Error> 的型別別名
  • fs::read_to_string / fs::write:最簡單的一行讀寫
  • File::open + BufReader:逐行讀取大檔案
  • File::create + writeln!:逐行寫入
  • ReadWriteBufRead 是輸入輸出的核心 trait,讓不同來源(檔案、stdin、網路)用同一套介面