本章介紹 Rust 語言的 I/O 操作。
命令行程序是計(jì)算機(jī)程序最基礎(chǔ)的存在形式,幾乎所有的操作系統(tǒng)都支持命令行程序并將可視化程序的運(yùn)行基于命令行機(jī)制。
命令行程序必須能夠接收來自命令行環(huán)境的參數(shù),這些參數(shù)往往在一條命令行的命令之后以空格符分隔。
在很多語言中(如 Java 和 C/C++)環(huán)境參數(shù)是以主函數(shù)的參數(shù)(常常是一個(gè)字符串?dāng)?shù)組)傳遞給程序的,但在 Rust 中主函數(shù)是個(gè)無參函數(shù),環(huán)境參數(shù)需要開發(fā)者通過 std::env 模塊取出,過程十分簡單:
fn main() { let args = std::env::args(); println!("{:?}", args); }
現(xiàn)在直接運(yùn)行程序:
Args { inner: ["D:\\rust\\greeting\\target\\debug\\greeting.exe"] }
也許你得到的結(jié)果比這個(gè)要長的多,這很正常,這個(gè)結(jié)果中 Args 結(jié)構(gòu)體中有一個(gè) inner 數(shù)組,只包含唯一的字符串,代表了當(dāng)前運(yùn)行的程序所在的位置。
但這個(gè)數(shù)據(jù)結(jié)構(gòu)令人難以理解,沒關(guān)系,我們可以簡單地遍歷它:
fn main() { let args = std::env::args(); for arg in args { println!("{}", arg); } }
運(yùn)行結(jié)果:
D:\rust\greeting\target\debug\greeting.exe
一般參數(shù)們就是用來被遍歷的,不是嗎。
現(xiàn)在我們打開許久未碰的 launch.json ,找到 "args": [],這里可以設(shè)置運(yùn)行時(shí)的參數(shù),我們將它寫成 "args": ["first", "second"] ,然后保存、再次運(yùn)行剛才的程序,運(yùn)行結(jié)果:
D:\rust\greeting\target\debug\greeting.exe first second
作為一個(gè)真正的命令行程序,我們從未真正使用過它,作為語言教程不在此敘述如何用命令行運(yùn)行 Rust 程序。但如果你是個(gè)訓(xùn)練有素的開發(fā)者,你應(yīng)該可以找到可執(zhí)行文件的位置,你可以嘗試進(jìn)入目錄并使用命令行命令來測試程序接收命令行環(huán)境參數(shù)。
早期的章節(jié)詳細(xì)講述了如何使用命令行輸出,這是由于語言學(xué)習(xí)的需要,沒有輸出是無法調(diào)試程序的。但從命令行獲取輸入的信息對于一個(gè)命令行程序來說依然是相當(dāng)重要的。
在 Rust 中,std::io 模塊提供了標(biāo)準(zhǔn)輸入(可認(rèn)為是命令行輸入)的相關(guān)功能:
use std::io::stdin; fn main() { let mut str_buf = String::new(); stdin().read_line(&mut str_buf) .expect("Failed to read line."); println!("Your input line is \n{}", str_buf); }
令 VSCode 環(huán)境支持命令行輸入是一個(gè)非常繁瑣的事情,牽扯到跨平臺的問題和不可調(diào)試的問題,所以我們直接在 VSCode 終端中運(yùn)行程序。在命令行中運(yùn)行:
D:\rust\greeting> cd ./target/debug D:\rust\greeting\target\debug> ./greeting.exe nhooo Your input line is nhooo
std::io::Stdio 包含 read_line 讀取方法,可以讀取一行字符串到緩沖區(qū),返回值都是 Result 枚舉類,用于傳遞讀取中出現(xiàn)的錯誤,所以常用 expect 或 unwrap 函數(shù)來處理錯誤。
注意:目前 Rust 標(biāo)準(zhǔn)庫還沒有提供直接從命令行讀取數(shù)字或格式化數(shù)據(jù)的方法,我們可以讀取一行字符串并使用字符串識別函數(shù)處理數(shù)據(jù)。
我們在計(jì)算機(jī)的 D:\ 目錄下建立文件 text.txt,內(nèi)容如下:
This is a text file.
這是一個(gè)將文本文件內(nèi)容讀入字符串的程序:
use std::fs; fn main() { let text = fs::read_to_string("D:\\text.txt").unwrap(); println!("{}", text); }
運(yùn)行結(jié)果:
This is a text file.
在 Rust 中讀取內(nèi)存可容納的一整個(gè)文件是一件極度簡單的事情,std::fs 模塊中的 read_to_string 方法可以輕松完成文本文件的讀取。
但如果要讀取的文件是二進(jìn)制文件,我們可以用 std::fs::read 函數(shù)讀取 u8 類型集合:
use std::fs; fn main() { let content = fs::read("D:\\text.txt").unwrap(); println!("{:?}", content); }
運(yùn)行結(jié)果:
[84, 104, 105, 115, 32, 105, 115, 32, 97, 32, 116, 101, 120, 116, 32, 102, 105, 108, 101, 46]
以上兩種方式是一次性讀取,十分適合 Web 應(yīng)用的開發(fā)。但是對于一些底層程序來說,傳統(tǒng)的按流讀取的方式依然是無法被取代的,因?yàn)楦嗲闆r下文件的大小可能遠(yuǎn)超內(nèi)存容量。
Rust 中的文件流讀取方式:
use std::io::prelude::*; use std::fs; fn main() { let mut buffer = [0u8; 5]; let mut file = fs::File::open("D:\\text.txt").unwrap(); file.read(&mut buffer).unwrap(); println!("{:?}", buffer); file.read(&mut buffer).unwrap(); println!("{:?}", buffer); }
運(yùn)行結(jié)果:
[84, 104, 105, 115, 32] [105, 115, 32, 97, 32]
std::fs 模塊中的 File 類是描述文件的類,可以用于打開文件,再打開文件之后,我們可以使用 File 的 read 方法按流讀取文件的下面一些字節(jié)到緩沖區(qū)(緩沖區(qū)是一個(gè) u8 數(shù)組),讀取的字節(jié)數(shù)等于緩沖區(qū)的長度。
注意:VSCode 目前還不具備自動添加標(biāo)準(zhǔn)庫引用的功能,所以有時(shí)出現(xiàn)"函數(shù)或方法不存在"一樣的錯誤有可能是標(biāo)準(zhǔn)庫引用的問題。我們可以查看標(biāo)準(zhǔn)庫的注釋文檔(鼠標(biāo)放到上面會出現(xiàn))來手動添加標(biāo)準(zhǔn)庫。
std::fs::File 的 open 方法是"只讀"打開文件,并且沒有配套的 close 方法,因?yàn)?Rust 編譯器可以在文件不再被使用時(shí)自動關(guān)閉文件。
文件寫入分為一次性寫入和流式寫入。流式寫入需要打開文件,打開方式有"新建"(create)和"追加"(append)兩種。
一次性寫入:
use std::fs; fn main() { fs::write("D:\\text.txt", "FROM RUST PROGRAM") .unwrap(); }
這和一次性讀取一樣簡單方便。執(zhí)行程序之后, D:\text.txt 文件的內(nèi)容將會被重寫為 FROM RUST PROGRAM 。所以,一次性寫入請謹(jǐn)慎使用!因?yàn)樗鼤苯觿h除文件內(nèi)容(無論文件多么大)。如果文件不存在就會創(chuàng)建文件。
如果想使用流的方式寫入文件內(nèi)容,可以使用 std::fs::File 的 create 方法:
use std::io::prelude::*; use std::fs::File; fn main() { let mut file = File::create("D:\\text.txt").unwrap(); file.write(b"FROM RUST PROGRAM").unwrap(); }
這段程序與上一個(gè)程序等價(jià)。
注意:打開的文件一定存放在可變的變量中才能使用 File 的方法!
File 類中不存在 append 靜態(tài)方法,但是我們可以使用 OpenOptions 來實(shí)現(xiàn)用特定方法打開文件:
use std::io::prelude::*; use std::fs::OpenOptions; fn main() -> std::io::Result<()> { let mut file = OpenOptions::new() .append(true).open("D:\\text.txt")?; file.write(b" APPEND WORD")?; Ok(()) }
運(yùn)行之后,D:\text.txt 文件內(nèi)容將變成:
FROM RUST PROGRAM APPEND WORD
OpenOptions 是一個(gè)靈活的打開文件的方法,它可以設(shè)置打開權(quán)限,除append 權(quán)限以外還有 read 權(quán)限和 write 權(quán)限,如果我們想以讀寫權(quán)限打開一個(gè)文件可以這樣寫:
use std::io::prelude::*; use std::fs::OpenOptions; fn main() -> std::io::Result<()> { let mut file = OpenOptions::new() .read(true).write(true).open("D:\\text.txt")?; file.write(b"COVER")?; Ok(()) }
運(yùn)行之后,D:\text.txt 文件內(nèi)容將變成:
COVERRUST PROGRAM APPEND WORD