Rust 配置文件內容及使用全面講解
問題
在之前的程序中,文本分割符皆以硬編碼的方式出現,導致程序靈活性較差。rzeo 項目解析的文本,我傾向為下例所示形式:
以下 Rust 程序
@ hello world #
fn main() {
println!("Hello world!");
}
@
可在終端打印「Hello world!」。但是對于其他 rzeo 的用戶而言,未必喜歡使用 @ 和 # 之類的符號。為了最大程度兼容所有人的偏好,rzeo 使用的文本分割符需以配置文件的方式進行定義,在其運行時方知文本分割符的具體形式。
rzeo 的配置文件采用 YAML 語言撰寫,例如:
border: '\n[ \t]*@[ \t\n]*' code_snippet_neck: '[ \t]*#[ \t]*\n'
假設 rzeo 配置文件為 rzeo.conf,編寫一個程序從該文件獲取分割符。
讀取文件
首先,考慮如何使用 Rust 標準庫提供的文件讀寫功能,讀取配置文件 rzeo.conf,并輸出其內容,以熟悉文件讀寫功能的基本用法。
以下代碼可讀取 rzeo.conf 文件并逐行打印其內容:
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 是實現了 BufRead 特性的結構體類型,用于將硬盤中的文件內容讀入到內存緩沖區(qū)以降低硬盤讀取次數。需要注意的是,File 的 open 方法和 BufReader 的 lines 方法皆返回 Result<T, std::io::Error> 類型,T 為 String 類型——Rust 的又一種字符串類型,相當于 Vec<char> 類型。在此不對 String 給予講解,可在使用中逐漸熟悉其用法。
以下代碼可將 rzeo.conf 文件中的內容寫入另一個文件:
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 方法,其參數類型為 &[u8],即字節(jié)數組切片,故而需要使用 &str 或 String 類型的 as_bytes 將字符串轉化為字節(jié)數組切片。write_all 的返回值是 Result<()> 類型,需使用 unwrap 解包或使用 ? 進行錯誤傳播。
路徑
為了保持不同操作系統(tǒng)中文件路徑的兼容性,Rust 標準庫提供了一種特殊的字符串類型 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 文件并不困難,困難的是對其內容的解析。當前的 rzeo.conf 文件中的內容,僅僅用到了 YAML 最為基礎的語法——鍵值對,即便如此,要對其予以解析,免不了要寫許多代碼。Rust 第三方庫 serde 能夠實現 Rust 語言的值與特定格式的數據文件的交換,即值的序列化(Serialize)和反序列化(Deserialize)。
serde 只是一個框架,對于特定格式的數據文件,需要引入 serde 的相應實現。下面使用 cargo 構建一個項目,引入 serde 和 serde_yaml 庫,實現 Rust 結構體的序列化。
首先,使用 cargo 建立新項目并進入項目目錄:
$ cargo new foo $ cd foo
然后使用 cargo add 命令添加 serde(同時開啟 serde 的 derive 特性)和 serde_yaml 庫:
$ cargo add -F derive serde $ cargo add serde_yaml
上述命令可在項目根目錄下的 Cargo.toml 文件的 [dependencies] 部分添加以下內容:
serde = { version = "1.0.171", features = ["derive"] }
serde_yaml = "0.9.22"隨著 serde 和 serde_yaml 庫的更新,等你看到這份文檔時,也動手搭建這個項目時,庫的版本號應該是與上述內容不同。
序列化與反序列化
編輯上一節(jié)構建的 foo 項目的 src/main.rs 文件,令其內容為
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í)行以下命令,編譯并運行程序:
$ cargo run
但是上述的 main.rs 中存在錯誤,導致 Rust 編譯器報錯。錯誤的原因是 File::create 和 sert_yaml::to_writer 返回的 Result<T, E> 類型不一致,導致無法給出 main 函數的返回值類型的正確定義。對于上述代碼快速而臟的修復是
serde_yaml::to_writer(f, &foo).unwrap();
即放棄 serde_yaml::to_write 的錯誤進行傳播。
上述程序通過編譯,運行結果是在當前目錄創(chuàng)建 foo.yml 文件,其內容為
id: 1
data: Hello world!
以下代碼實現了 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 中的內容被轉換為 Rust 結構體類型 Foo 的實例 foo。需要注意的是,上述 Foo 的 data 域,其類型不再是 &str,而是 String,原因 serde_yaml::from_reader 方法并不占有數據,導致存儲反序列化結果的結構體實例中的引用無效。
分割符
現在,定義一個結構體類型 Separator,用于存儲從 rzeo.conf 中獲取的分割符:
#[derive(Debug, serde::Deserialize)]
struct Separator {
border: String,
code_snippet_neck: String
}使用以下代碼便可解析 rzeo.conf 相應的信息并將解析結果作為 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 語言編寫的庫也能夠實現對 YAML 文件的解析,例如能夠支持 C 語言結構體的 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;
/* 構造結構體類型 Foo 與 rzeo.conf 之間的聯系 */
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)
};
/* 解析器設置 */
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)
運行程序:
$ ./foo border: \n[ \t]*@[ \t\n]* code_snippet_neck: [ \t]*#[ \t]*\n
小結
Rust 第三方庫對 YAML 序列化和反序列化的支持優(yōu)于 C 的第三方庫。必須要承認,C 語言在文本處理方面,若想變得更為優(yōu)雅,最好的辦法是先基于它實現一門小巧的動態(tài)語言(例如 Lua 語言),由后者負責處理文本。
以上就是Rust 配置文件內容及使用全面講解的詳細內容,更多關于Rust 配置文件的資料請關注腳本之家其它相關文章!
相關文章
Rust可迭代類型迭代器正確創(chuàng)建自定義可迭代類型的方法
在 Rust 中, 如果一個類型實現了 Iterator, 那么它會被同時實現 IntoIterator, 具體邏輯是返回自身, 因為自身就是迭代器,這篇文章主要介紹了Rust可迭代類型迭代器正確創(chuàng)建自定義可迭代類型的方法,需要的朋友可以參考下2023-12-12

