Rust 配置文件內(nèi)容及使用全面講解
問題
在之前的程序中,文本分割符皆以硬編碼的方式出現(xiàn),導(dǎo)致程序靈活性較差。rzeo 項(xiàng)目解析的文本,我傾向?yàn)橄吕拘问剑?/p>
以下 Rust 程序 @ hello world # fn main() { println!("Hello world!"); } @ 可在終端打印「Hello world!」。
但是對于其他 rzeo 的用戶而言,未必喜歡使用 @
和 #
之類的符號(hào)。為了最大程度兼容所有人的偏好,rzeo 使用的文本分割符需以配置文件的方式進(jìn)行定義,在其運(yùn)行時(shí)方知文本分割符的具體形式。
rzeo 的配置文件采用 YAML 語言撰寫,例如:
border: '\n[ \t]*@[ \t\n]*' code_snippet_neck: '[ \t]*#[ \t]*\n'
假設(shè) rzeo 配置文件為 rzeo.conf,編寫一個(gè)程序從該文件獲取分割符。
讀取文件
首先,考慮如何使用 Rust 標(biāo)準(zhǔn)庫提供的文件讀寫功能,讀取配置文件 rzeo.conf,并輸出其內(nèi)容,以熟悉文件讀寫功能的基本用法。
以下代碼可讀取 rzeo.conf 文件并逐行打印其內(nèi)容:
use std::fs::File; use std::io::{BufRead, BufReader}; fn main() -> Result<(), std::io::Error> { let f = File::open("rzeo.conf")?; let reader = BufReader::new(f); for line in reader.lines() { println!("{}", line?); } return Ok(()); }
BufRead
是特性。BufReader
是實(shí)現(xiàn)了 BufRead
特性的結(jié)構(gòu)體類型,用于將硬盤中的文件內(nèi)容讀入到內(nèi)存緩沖區(qū)以降低硬盤讀取次數(shù)。需要注意的是,File
的 open
方法和 BufReader
的 lines
方法皆返回 Result<T, std::io::Error>
類型,T
為 String
類型——Rust 的又一種字符串類型,相當(dāng)于 Vec<char>
類型。在此不對 String
給予講解,可在使用中逐漸熟悉其用法。
以下代碼可將 rzeo.conf 文件中的內(nèi)容寫入另一個(gè)文件:
use std::fs::File; use std::io::{BufRead, BufReader, Write}; fn main() -> Result<(), std::io::Error> { let f = File::open("rzeo.conf")?; let mut g = File::create("foo.txt")?; let reader = BufReader::new(f); for line in reader.lines() { let content = line?; g.write_all(content.as_bytes())?; g.write_all("\n".as_bytes())?; } return Ok(()); }
注意,File
的 write_all
方法,其參數(shù)類型為 &[u8]
,即字節(jié)數(shù)組切片,故而需要使用 &str
或 String
類型的 as_bytes
將字符串轉(zhuǎn)化為字節(jié)數(shù)組切片。write_all
的返回值是 Result<()>
類型,需使用 unwrap
解包或使用 ?
進(jìn)行錯(cuò)誤傳播。
路徑
為了保持不同操作系統(tǒng)中文件路徑的兼容性,Rust 標(biāo)準(zhǔn)庫提供了一種特殊的字符串類型 std::path::Path
以及一些用于處理文件路徑的方法。為了程序的可移植性,建議使用 std::path::Path
代替普通的字符串作為文件路徑。
以下代碼演示了 std::path::Path
的基本用法:
use std::path::Path; use std::fs::File; use std::io::{BufRead, BufReader}; fn main() -> Result<(), std::io::Error> { let path = Path::new("/tmp/rzeo.conf"); println!("{:?}", path); let f = File::open(path)?; let reader = BufReader::new(f); for line in reader.lines() { println!("{}", line?); } return Ok(()); }
引入 serde 庫
讀取 rzeo.conf 文件并不困難,困難的是對其內(nèi)容的解析。當(dāng)前的 rzeo.conf 文件中的內(nèi)容,僅僅用到了 YAML 最為基礎(chǔ)的語法——鍵值對,即便如此,要對其予以解析,免不了要寫許多代碼。Rust 第三方庫 serde 能夠?qū)崿F(xiàn) Rust 語言的值與特定格式的數(shù)據(jù)文件的交換,即值的序列化(Serialize)和反序列化(Deserialize)。
serde 只是一個(gè)框架,對于特定格式的數(shù)據(jù)文件,需要引入 serde 的相應(yīng)實(shí)現(xiàn)。下面使用 cargo 構(gòu)建一個(gè)項(xiàng)目,引入 serde 和 serde_yaml 庫,實(shí)現(xiàn) Rust 結(jié)構(gòu)體的序列化。
首先,使用 cargo 建立新項(xiàng)目并進(jìn)入項(xiàng)目目錄:
$ cargo new foo $ cd foo
然后使用 cargo add
命令添加 serde(同時(shí)開啟 serde 的 derive 特性)和 serde_yaml 庫:
$ cargo add -F derive serde $ cargo add serde_yaml
上述命令可在項(xiàng)目根目錄下的 Cargo.toml 文件的 [dependencies]
部分添加以下內(nèi)容:
serde = { version = "1.0.171", features = ["derive"] } serde_yaml = "0.9.22"
隨著 serde 和 serde_yaml 庫的更新,等你看到這份文檔時(shí),也動(dòng)手搭建這個(gè)項(xiàng)目時(shí),庫的版本號(hào)應(yīng)該是與上述內(nèi)容不同。
序列化與反序列化
編輯上一節(jié)構(gòu)建的 foo 項(xiàng)目的 src/main.rs 文件,令其內(nèi)容為
use std::fs::File; #[derive(serde::Serialize)] struct Foo<'a> { id: u32, data: &'a str } fn main() -> Result<(), std::io::Error> { let foo = Foo { id: 1, data: "Hello world!" }; let f = File::create("foo.yml")?; serde_yaml::to_writer(f, &foo)?; return Ok(()); }
執(zhí)行以下命令,編譯并運(yùn)行程序:
$ cargo run
但是上述的 main.rs 中存在錯(cuò)誤,導(dǎo)致 Rust 編譯器報(bào)錯(cuò)。錯(cuò)誤的原因是 File::create
和 sert_yaml::to_writer
返回的 Result<T, E>
類型不一致,導(dǎo)致無法給出 main
函數(shù)的返回值類型的正確定義。對于上述代碼快速而臟的修復(fù)是
serde_yaml::to_writer(f, &foo).unwrap();
即放棄 serde_yaml::to_write
的錯(cuò)誤進(jìn)行傳播。
上述程序通過編譯,運(yùn)行結(jié)果是在當(dāng)前目錄創(chuàng)建 foo.yml 文件,其內(nèi)容為
id: 1
data: Hello world!
以下代碼實(shí)現(xiàn)了 YAML 文件 foo.yml 的反序列化:
use std::fs::File; #[derive(Debug, serde::Deserialize)] struct Foo { id: u32, data: String } fn main() -> Result<(), std::io::Error> { let f = File::open("foo.yml")?; let foo: Foo = serde_yaml::from_reader(f).unwrap(); println!("{:?}", foo); return Ok(()); }
foo.yml 中的內(nèi)容被轉(zhuǎn)換為 Rust 結(jié)構(gòu)體類型 Foo
的實(shí)例 foo
。需要注意的是,上述 Foo
的 data
域,其類型不再是 &str
,而是 String
,原因 serde_yaml::from_reader
方法并不占有數(shù)據(jù),導(dǎo)致存儲(chǔ)反序列化結(jié)果的結(jié)構(gòu)體實(shí)例中的引用無效。
分割符
現(xiàn)在,定義一個(gè)結(jié)構(gòu)體類型 Separator,用于存儲(chǔ)從 rzeo.conf 中獲取的分割符:
#[derive(Debug, serde::Deserialize)] struct Separator { border: String, code_snippet_neck: String }
使用以下代碼便可解析 rzeo.conf 相應(yīng)的信息并將解析結(jié)果作為 Separator
類型的值:
fn main() -> Result<(), std::io::Error> { let f = std::fs::File::open("rzeo.conf")?; let foo: Separator = serde_yaml::from_reader(f).unwrap(); println!("{:?}", foo); return Ok(()); }
至此,本章開頭提出的問題便得以解決。
C 版本
有一些采用 C 語言編寫的庫也能夠?qū)崿F(xiàn)對 YAML 文件的解析,例如能夠支持 C 語言結(jié)構(gòu)體的 YAML 序列化和反序列化的庫 libcyaml,在 Ubuntu 系統(tǒng)可使用以下命令安裝該庫:
$ sudo apt install libyaml-dev libcyaml-dev
以下是反序列化 rzeo.conf 文件的 C 程序:
#include <stdlib.h> #include <stdio.h> #include <cyaml/cyaml.h> typedef struct { char *border; char *code_snippet_neck; } Foo; /* 構(gòu)造結(jié)構(gòu)體類型 Foo 與 rzeo.conf 之間的聯(lián)系 */ static const cyaml_schema_field_t top_mapping[] = { CYAML_FIELD_STRING_PTR( "border", CYAML_FLAG_POINTER, Foo, border, 0, CYAML_UNLIMITED), CYAML_FIELD_STRING_PTR( "code_snippet_neck", CYAML_FLAG_POINTER, Foo, code_snippet_neck, 0, CYAML_UNLIMITED), CYAML_FIELD_END }; static const cyaml_schema_value_t top = { CYAML_VALUE_MAPPING(CYAML_FLAG_POINTER, Foo, top_mapping) }; /* 解析器設(shè)置 */ static const cyaml_config_t config = { .log_fn = cyaml_log, .mem_fn = cyaml_mem, .log_level = CYAML_LOG_WARNING }; /* 反序列化 */ int main(void) { Foo *foo; cyaml_err_t err = cyaml_load_file("rzeo.conf", &config, &top, (cyaml_data_t **)&foo, NULL); if (err != CYAML_OK) { fprintf(stderr, "ERROR: %s\n", cyaml_strerror(err)); return EXIT_FAILURE; } printf("border: %s\n", foo->border); printf("code_snippet_neck: %s\n", foo->code_snippet_neck); cyaml_free(&config, &top, foo, 0); return 0; }
使用以下命令編譯上述程序:
$ gcc -o foo foo.c $(pkg-config --cflags --libs libcyaml)
運(yùn)行程序:
$ ./foo border: \n[ \t]*@[ \t\n]* code_snippet_neck: [ \t]*#[ \t]*\n
小結(jié)
Rust 第三方庫對 YAML 序列化和反序列化的支持優(yōu)于 C 的第三方庫。必須要承認(rèn),C 語言在文本處理方面,若想變得更為優(yōu)雅,最好的辦法是先基于它實(shí)現(xiàn)一門小巧的動(dòng)態(tài)語言(例如 Lua 語言),由后者負(fù)責(zé)處理文本。
以上就是Rust 配置文件內(nèi)容及使用全面講解的詳細(xì)內(nèi)容,更多關(guān)于Rust 配置文件的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Rust使用lettre實(shí)現(xiàn)郵件發(fā)送功能
這篇文章主要為大家詳細(xì)介紹了Rust如何使用lettre實(shí)現(xiàn)郵件發(fā)送功能,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2024-11-11使用vscode配置Rust運(yùn)行環(huán)境全過程
VS Code對Rust有著較完備的支持,這篇文章主要給大家介紹了關(guān)于使用vscode配置Rust運(yùn)行環(huán)境的相關(guān)資料,文中通過圖文介紹的非常詳細(xì),需要的朋友可以參考下2023-06-06Rust可迭代類型迭代器正確創(chuàng)建自定義可迭代類型的方法
在 Rust 中, 如果一個(gè)類型實(shí)現(xiàn)了 Iterator, 那么它會(huì)被同時(shí)實(shí)現(xiàn) IntoIterator, 具體邏輯是返回自身, 因?yàn)樽陨砭褪堑?這篇文章主要介紹了Rust可迭代類型迭代器正確創(chuàng)建自定義可迭代類型的方法,需要的朋友可以參考下2023-12-12Rust調(diào)用C程序的實(shí)現(xiàn)步驟
本文主要介紹了Rust調(diào)用C程序的實(shí)現(xiàn)步驟,包括創(chuàng)建C函數(shù)、編譯C代碼、鏈接Rust和C代碼等步驟,具有一定的參考價(jià)值,感興趣的可以了解一下2023-12-12利用rust實(shí)現(xiàn)一個(gè)命令行工具
這篇文章主要為大家詳細(xì)介紹了如何使用?Rust?和?clap?4.4.0?創(chuàng)建一個(gè)命令行工具?my_dev_tool,文中的示例代碼講解詳細(xì),需要的小伙伴可以參考下2023-12-12