【跟小嘉學 Rust 編程】十二、構建一個命令行程序

系列文章目錄

【跟小嘉學 Rust 編程】一、Rust 編程基礎
【跟小嘉學 Rust 編程】二、Rust 包管理工具使用
【跟小嘉學 Rust 編程】三、Rust 的基本程序概念
【跟小嘉學 Rust 編程】四、理解 Rust 的所有權概念
【跟小嘉學 Rust 編程】五、使用結構體關聯結構化數據
【跟小嘉學 Rust 編程】六、枚舉和模式匹配
【跟小嘉學 Rust 編程】七、使用包(Packages)、單元包(Crates)和模塊(Module)來管理項目
【跟小嘉學 Rust 編程】八、常見的集合
【跟小嘉學 Rust 編程】九、錯誤處理(Error Handling)
【跟小嘉學 Rust 編程】十一、編寫自動化測試
【跟小嘉學 Rust 編程】十二、構建一個命令行程序

文章目錄

  • 系列文章目錄
    • @[TOC](文章目錄)
  • 前言
  • 一、如何接受命令行參數
    • 1.1、創建項目
    • 1.2、需求介紹
    • 1.3、讀取參數值
    • 1.4、將參數值保存進變量
  • 二、讀取文件(使用 fs 模塊讀取文件)
  • 三、模塊化與錯誤處理
    • 3.1、代碼中存在的問題
    • 3.2、二進制項目的關注分離
    • 3.3、提取參數解析器
    • 3.4、使用結構來組織配置變量
    • 3.5、創建 Config 的構造函數
    • 3.6、修復錯誤處理
      • 3.6.1、改善錯誤提示
      • 3.6.2、業務邏輯處理:run方法
      • 3.6.3、run 函數返回 Result 錯誤
    • 3.7、將代碼拆分
      • 3.7.1、lib.rs
      • 3.7.2、main.rs
  • 四、測試驅動開發(TDD)
    • 4.1、search 方法編寫和測試
    • 4.2、在 run 函數中使用 search 函數
  • 五、處理環境變量
  • 六、標準輸出和標準錯誤
    • 6.1、標準輸出:stdout
    • 6.2、標準錯誤:stderr
  • 總結

前言

本章是一個目前所學的很多技能的應用,以及標準庫的探索,我們講構建一個命令行程序工具來練習現在已經學習過的一些Rust的技能。我們將構建自己的版本的命令行工具:grep(Globally search a Regular Expression and print)。

主要教材參考 《The Rust Programming Language》


一、如何接受命令行參數

1.1、創建項目

$ cargo new minigrepCreated binary (application) `minigrep` project
$ cd minigrep

1.2、需求介紹

minigrep 能夠接受兩個命令行參數:文件名和要搜索的字符串。也就是說我們希望使用cargo run的時候,可以使用如下的方式。

cargo run searchstring example-filename.txt

在 Crates.io 上會有一些現場的庫幫助我們接受命令行參數(clap)。不過我們現階段使用標準庫。

1.3、讀取參數值

為了能夠接受命令行參數的值,我們需要使用 rust 標準庫提供的函數。該函數返回一個命令行參數的迭代器(iiterator),迭代器我們將會在下一章詳細講解。我們只需要知道在迭代器上有一個方法 collect 可以將其轉換為一個集合。

use std::env;fn main() {let args: Vec<String> = env::args().collect();println!("{:?}", args);
}

需要注意 args 函數 在其任何參數包含 無效Unicode 字符時會panic。 如果你需要接受包含無效Unicode字符的參數,使用 std::env::args_os 代替。該函數返回 OsString值而不是 String 值。

Vector 的第一個參數是二進制文件的名稱。

1.4、將參數值保存進變量

use std::env;fn main() {let args: Vec<String> = env::args().collect();let query = &args[1];let filename = &args[2];println!("Searching for {}", query);println!("In file {}", filename);
}

二、讀取文件(使用 fs 模塊讀取文件)

use std::{env, fs};fn main() {let args: Vec<String> = env::args().collect();let query = &args[1];let filename = &args[2];println!("Searching for {}", query);println!("In file {}", filename);let contents = fs::read_to_string(filename).expect("Something went wrong reading the file");println!("With text:\n{}", contents);}

fs::read_to_string(filename) 方法打開文件,返回包含內容的Result<String>

三、模塊化與錯誤處理

我們上述代碼 main 函數有著多個職責,通常函數只負責一個功能會更加簡潔并且易于維護。在開發的時候重構是一個最佳時間,重構少量代碼要容易的多。

3.1、代碼中存在的問題

我們最初的代碼存在下面四個問題:

  • 1、 main 現在進行了兩個功能:解析參數并且打開文件。但是當函數承擔了更多責任,會更加難易推導,難以測試,并且難以在不破壞其他部分的情況下做出修改。
  • 2、query 和 flename 是程序中過的配置i變了,而 contents 則用來執行程序邏輯。當變量越來越多的時候便會難以追蹤分析每個變量的目的,最好能夠講配置變量組織進一個結構。這樣就能夠使他們的目的更加明確;
  • 3、如果打開文件失敗 ,我們使用 expect 來打印錯誤信息,不過這種錯誤信息并不明確,讀取文件失敗的原因有很多種:例如文件不存在,或者沒有打開文件的權限等,無論那種情況,這并沒有給予使用者具體的信息
  • 4、我們不停的使用 expect 來處理不同的錯誤,如果用戶沒有指定足夠的的參數來運行程序,他們會從 rust 中得到 一個 index out of bounds 錯誤,而這并不能明確解釋問題。如果所有的錯誤處理都位于一處,這樣將來的維護者需要在修改錯誤處理邏輯時只需要考慮這一處代碼。

3.2、二進制項目的關注分離

main 函數負責多個任務的組織問題在許多二進制項目中很常見。所以 Rust 社區開發出一類在 main 函數開始變得龐大時進行二進制程序的關注分離的指導性過程。這些過程有如下步驟:

  • 將程序拆分成 main.rs 和 lib.rs 并將程序的邏輯放入 lib.rs 中。
  • 當命令行解析邏輯比較小時,可以保留在 main.rs 中。
  • 當命令行解析開始變得復雜時,也同樣將其從 main.rs 提取到 lib.rs 中。

經過這些過程之后保留在 main 函數中的責任應該被限制為:

  • 使用參數值調用命令行解析邏輯
  • 設置任何其他的配置
  • 調用 lib.rs 中的 run 函數
  • 如果 run 返回錯誤,則處理這個錯誤

這個模式的一切就是為了關注分離:main.rs 處理程序運行,而 lib.rs 處理所有的真正的任務邏輯。因為不能直接測試 main 函數,這個結構通過將所有的程序邏輯移動到 lib.rs 的函數中使得我們可以測試他們。僅僅保留在 main.rs 中的代碼將足夠小以便閱讀就可以驗證其正確性。讓我們遵循這些步驟來重構程序

3.3、提取參數解析器

use std::{env, fs};fn main() {let args: Vec<String> = env::args().collect();let (query,filename) = parse_config(&args);println!("Searching for {}", query);println!("In file {}", filename);let contents = fs::read_to_string(filename).expect("Something went wrong reading the file");println!("With text:\n{}", contents);}fn parse_config(args: &[String]) -> (&str, &str) {let query = &args[1];let filename = &args[2];(query, filename)
}

3.4、使用結構來組織配置變量

use std::{env, fs};fn main() {let args: Vec<String> = env::args().collect();let config = parse_config(&args);println!("Searching for {}", config.query);println!("In file {}", config.filename);let contents = fs::read_to_string(config.filename).expect("Something went wrong reading the file");println!("With text:\n{}", contents);}struct Config {query: String,filename: String,
}fn parse_config(args: &[String]) -> Config {let query = args[1].clone();let filename = args[2].clone();Config { query, filename }
}

我們需要注意 我們定義的 Config 包含擁有所有權的String值,我們返回來引用 args 中的 String值的字符串切片 slice。 main函數的args變量是參數值的所有者并只允許 parse_config 方法借用他們。這意味著 Config 嘗試獲取args 中的值的所有權將違反 Rust的借用規則。

還有許多不同的方式可以處理 String 的數據,而最簡單但有些不太高效的方式是調用這些值的 clone 方法。這會生成 Config 實例可以擁有的數據的完整拷貝,不過會比儲存字符串數據的引用消耗更多的時間和內存。不過拷貝數據使得代碼顯得更加直白因為無需管理引用的生命周期,所以在這種情況下犧牲一小部分性能來換取簡潔性的取舍是值得的。

由于其運行時消耗,許多 Rustacean 之間有一個趨勢是傾向于避免使用 clone 來解決所有權問題。

在關于迭代器的章節中,我們將學習如何更加有效率的處理這種情況,不過現在復制字符串取得進展是沒有問題的。因為只會進行一次這樣的拷貝,而且文件名和要搜索的字符串都比較短。

3.5、創建 Config 的構造函數

use std::{env, fs};fn main() {let args: Vec<String> = env::args().collect();let config = Config::new(&args);println!("Searching for {}", config.query);println!("In file {}", config.filename);let contents = fs::read_to_string(config.filename).expect("Something went wrong reading the file");println!("With text:\n{}", contents);}struct Config {query: String,filename: String,
}impl Config {fn new(args: &[String])-> Config{let query = args[1].clone();let filename = args[2].clone();Config { query, filename }}
} 

3.6、修復錯誤處理

3.6.1、改善錯誤提示

對于錯誤,我們可以使用 panic!,但是 panic!更趨向于程序上的問題,而不是使用上的問題,我們應該使用Result 枚舉來處理錯誤。

use std::{env, fs, process};const ARGS_LENGTH:usize= 3;fn main() {let args: Vec<String> = env::args().collect();let config = Config::new(&args).unwrap_or_else(|err|{println!("Problem parsing arguments:{}", err);process::exit(1);});println!("Searching for {}", config.query);println!("In file {}", config.filename);let contents = fs::read_to_string(config.filename).expect("Something went wrong reading the file");println!("With text:\n{}", contents);}struct Config {query: String,filename: String,
}impl Config {fn new(args: &[String])-> Result<Config, &'static str>{if args.len() < ARGS_LENGTH  {return Err("not enough arguments")}let query = args[1].clone();let filename = args[2].clone();Ok( Config { query, filename })}
} 

3.6.2、業務邏輯處理:run方法

use std::{env, fs, process};const ARGS_LENGTH:usize= 3;fn main() {let args: Vec<String> = env::args().collect();let config = Config::new(&args).unwrap_or_else(|err|{println!("Problem parsing arguments:{}", err);process::exit(1);});println!("Searching for {}", config.query);println!("In file {}", config.filename);run(config);
}struct Config {query: String,filename: String,
}impl Config {fn new(args: &[String])-> Result<Config, &'static str>{if args.len() < ARGS_LENGTH  {return Err("not enough arguments")}let query = args[1].clone();let filename = args[2].clone();Ok( Config { query, filename })}
} fn run(config: Config) {let contents = fs::read_to_string(config.filename).expect("Something went wrong reading the file");println!("With text:\n{}", contents);
}

3.6.3、run 函數返回 Result 錯誤

use std::{env, fs, process, error::Error};const ARGS_LENGTH:usize= 3;fn main() {let args: Vec<String> = env::args().collect();let config = Config::new(&args).unwrap_or_else(|err|{println!("Problem parsing arguments:{}", err);process::exit(1);});println!("Searching for {}", config.query);println!("In file {}", config.filename);if let Err(e) = run(config) {println!(" Application error: {}", e);process::exit(1);}
}struct Config {query: String,filename: String,
}impl Config {fn new(args: &[String])-> Result<Config, &'static str>{if args.len() < ARGS_LENGTH  {return Err("not enough arguments")}let query = args[1].clone();let filename = args[2].clone();Ok( Config { query, filename })}
} fn run(config: Config) -> Result<(), Box<dyn Error>>{let contents = fs::read_to_string(config.filename)?;println!("With text:\n{}", contents);Ok(())
}

3.7、將代碼拆分

3.7.1、lib.rs


use std::{fs, error::Error};const ARGS_LENGTH:usize= 3;pub struct Config {pub query: String,pub filename: String,
}impl Config {pub fn new(args: &[String])-> Result<Config, &'static str>{if args.len() < ARGS_LENGTH  {return Err("not enough arguments")}let query = args[1].clone();let filename = args[2].clone();Ok( Config { query, filename })}
} pub fn run(config: Config) -> Result<(), Box<dyn Error>>{let contents = fs::read_to_string(config.filename)?;println!("With text:\n{}", contents);Ok(())
}

3.7.2、main.rs

use std::{env, process};
use minigrep::Config;fn main() {let args: Vec<String> = env::args().collect();let config = Config::new(&args).unwrap_or_else(|err|{println!("Problem parsing arguments:{}", err);process::exit(1);});println!("Searching for {}", config.query);println!("In file {}", config.filename);if let Err(e) = minigrep::run(config) {println!(" Application error: {}", e);process::exit(1);}
}

四、測試驅動開發(TDD)

4.1、search 方法編寫和測試

pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {let mut result = Vec::new();for line in contents.lines(){if line.contains(query) {result.push(line);}}println!("{:?}", result);result
}#[cfg(test)]
mod tests {use super::*;#[test]fn one_result() {let query = "duct";let contents = "\
Rust:
safe, fast, productive.
Pick, three.
Duct tape.";assert_eq!(vec!["safe, fast, productive."],search(query, contents));}
}

4.2、在 run 函數中使用 search 函數

pub fn run(config: Config) -> Result<(), Box<dyn Error>>{let contents = fs::read_to_string(config.filename)?;for line in search(&config.query, &contents) {println!("{}", line);}Ok(())
}

五、處理環境變量


use std::{fs, error::Error, env};const ARGS_LENGTH:usize= 3;pub struct Config {pub query: String,pub filename: String,pub case_sensitive: bool,
}impl Config {pub fn new(args: &[String])-> Result<Config, &'static str>{if args.len() < ARGS_LENGTH  {return Err("not enough arguments")}let query = args[1].clone();let filename = args[2].clone();let case_sensitive = env::var("CASE_INSENSITIVE").is_err();Ok( Config { query, filename , case_sensitive})}
} pub fn run(config: Config) -> Result<(), Box<dyn Error>>{let contents = fs::read_to_string(config.filename)?;for line in search(&config.query, &contents, config.case_sensitive) {println!("{}", line);}Ok(())
}pub fn search<'a>(query: &str, contents: &'a str, case_sensitive: bool) -> Vec<&'a str> {let mut result = Vec::new();if case_sensitive {let query_ignore_sensitive = query.to_lowercase();for line in contents.lines(){if line.to_lowercase().contains(&query_ignore_sensitive) {result.push(line);}}return result;} else {for line in contents.lines(){if line.contains(&query) {result.push(line);}}return result;}
}#[cfg(test)]
mod tests {use super::*;#[test]fn one_result() {let query = "duct";let contents = "\
Rust:
safe, fast, productive.
Pick, three.
Duct tape.";assert_eq!(vec!["safe, fast, productive."],search(query, contents));}
}

六、標準輸出和標準錯誤

6.1、標準輸出:stdout

println!() 宏就是把輸出信息輸出到標準輸出

6.2、標準錯誤:stderr

eprintln!() 宏就是把輸出信息輸出到標準錯誤

use std::{env, process};use minigrep::Config;fn main() {let args: Vec<String> = env::args().collect();let config = Config::new(&args).unwrap_or_else(|err|{eprintln!("Problem parsing arguments:{}", err);process::exit(1);});println!("Searching for {}", config.query);println!("In file {}", config.filename);println!("case_sensitive: {}", config.case_sensitive);if let Err(e) = minigrep::run(config) {eprintln!(" Application error: {}", e);process::exit(1);}
}

總結

以上就是今天要講的內容

  • 主要講解了一個項目的編寫過程

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/36226.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/36226.shtml
英文地址,請注明出處:http://en.pswp.cn/news/36226.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

【Pytroch】基于K鄰近算法的數據分類預測(Excel可直接替換數據)

【Pytroch】基于K鄰近算法的數據分類預測&#xff08;Excel可直接替換數據&#xff09; 1.模型原理2.數學公式3.文件結構4.Excel數據5.下載地址6.完整代碼7.運行結果 1.模型原理 K最近鄰&#xff08;K-Nearest Neighbors&#xff0c;簡稱KNN&#xff09;是一種簡單但常用的機器…

Redis基礎學習

目錄 第一章、Redis數據庫的下載和安裝1.1&#xff09;nosql數據庫和 Redis 介紹1.2&#xff09;Windows中下載安裝Redis數據庫1.3&#xff09;Linux中安裝Redis數據庫1.4&#xff09;Linux中啟動redis1.5&#xff09;Linux中關閉redis 第二章、三種Redis客戶端連接Redis數據庫…

安全遠控如何設置?揭秘ToDesk、TeamViewer 、向日葵安全遠程防御大招

寫在前面一、遠程控制&#xff1a;安全性不可忽略二、遠控軟件安全設置實測? ToDesk? TeamViewer? 向日葵 三、遠控安全的亮點功能四、個人總結與建議 寫在前面 說到遠程辦公&#xff0c;相信大家都不陌生。遠程工作是員工在家中或者其他非辦公場所上班的一種工作模式&…

傳輸層協議

傳輸層協議 再談端口號端口號范圍劃分認識知名端口號兩個問題netstatpidof UDP協議UDP協議端格式UDP的特點面向數據報UDP的緩沖區UDP使用注意事項基于UDP的應用層協議 TCP協議TCP協議段格式確認應答(ACK)機制超時重傳機制連接管理機制理解 CLOSE_WAIT 狀態理解TIME_WAIT狀態解決…

修改el-select和el-input樣式;修改element-plus的下拉框el-select樣式

修改el-select樣式 .select_box{// 默認placeholder:deep .el-input__inner::placeholder {font-size: 14px;font-weight: 500;color: #3E534F;}// 默認框狀態樣式更改:deep .el-input__wrapper {height: 42px;background-color: rgba(0,0,0,0)!important;box-shadow: 0 0 0 …

OptaPlanner筆記6 N皇后

N 個皇后 問題描述 將n個皇后放在n大小的棋盤上&#xff0c;沒有兩個皇后可以互相攻擊。 最常見的 n 個皇后謎題是八個皇后謎題&#xff0c;n 8&#xff1a; 約束&#xff1a; 使用 n 列和 n 行的棋盤。在棋盤上放置n個皇后。沒有兩個女王可以互相攻擊。女王可以攻擊同一水…

如何做好一名網絡工程師?具體實踐?

預防問題 – 資格與認證 在安裝線纜或升級網絡時測試線纜是預防問題的有效方式。對已安裝布線進行測試的方法有兩種。 資格測試確定布線是否有資格執行某些操作 — 換言之&#xff0c;支持特定網絡速度或應用。盡管“通過”認證測試也表明按標準支持某一網絡速度或應用的能力…

Redux - Redux在React函數式組件中的基本使用

文章目錄 一&#xff0c;簡介二&#xff0c;安裝三&#xff0c;三大核心概念Store、Action、Reducer3.1 Store3.2 Reducer3.3 Action 四&#xff0c;開始函數式組件中使用4.1&#xff0c;引入store4.1&#xff0c;store.getState()方法4.3&#xff0c;store.dispatch()方法4.4&…

cookie和session的區別及原理

Cookie概念 在瀏覽某些 網站 時,這些網站會把 一些數據存在 客戶端 , 用于使用網站 等跟蹤用戶,實現用戶自定義 功能. 是否設置過期時間: 如果不設置 過期時間,則表示這個 Cookie生命周期為 瀏覽器會話期間 , 只要關閉瀏覽器,cookie就消失了. 這個生命期為瀏覽會話期的cookie,就…

其他行業跳槽轉入計算機領域簡單看法

其他行業跳槽轉入計算機領域簡單看法 本人選擇從以下幾個方向談談自己的想法和觀點。 如何規劃才能實現轉碼 自我評估和目標設定&#xff1a;首先&#xff0c;你需要評估自己的技能和興趣&#xff0c;確定你希望在計算機領域從事的具體職位或領域。這有助于你更好地規劃學習路…

深入了解 Rancher Desktop 設置

Rancher Desktop 設置的全面概述 Rancher Desktop 擁有方便、強大的功能&#xff0c;是最佳的開發者工具之一&#xff0c;也是在本地構建和部署 Kubernetes 的最快捷方式。 本文將介紹 Rancher Desktop 的功能和特性&#xff0c;以及 Rancher Desktop 作為容器管理平臺和本地…

人工智能原理(2)

目錄 一、知識與知識表示 1、知識 2、知識表示 3、知識表示方法 二、謂詞邏輯表示法 1、命題邏輯 2、謂詞邏輯 三、產生式表達法 1、知識的表示方法 2、產生式系統組成 3、推理方式 4、產生式表示法特點 四、語義網絡 1、概念及結構 2、語義網絡的基本語義聯系 …

zookeeper案例

目錄 案例一&#xff1a;服務器動態上下線 服務端&#xff1a; &#xff08;1&#xff09;先獲取zookeeper連接 &#xff08;2&#xff09;注冊服務器到zookeeper集群&#xff1a; &#xff08;3&#xff09;業務邏輯&#xff08;睡眠&#xff09;&#xff1a; 服務端代碼…

Java+Excel+POI+testNG基于數據驅動做一個簡單的接口測試【杭州多測師_王sir】

一、創建一個apicases.xlsx放入到eclipse的resource里面&#xff0c;然后refresh刷新一下 二、在pom.xml文件中加入poi和testng的mvn repository、然后在eclipse的對應目錄下放入features和plugins&#xff0c;重啟eclipse就可以看到testNG了 <!--poi excel解析 --><d…

運維監控學習筆記3

DELL的IPMI頁面的登錄&#xff1a; 風扇的狀態&#xff1a; 電源溫度&#xff1a;超過70度就告警&#xff1a; 日志信息&#xff1a; 可以看到更換過磁盤。 iDRAC的設置 虛擬控制臺&#xff1a;啟動遠程控制臺&#xff1a; 可以進行遠程控制。 機房工程師幫我們接遠程控制&…

【云原生】kubernetes中容器的資源限制

目錄 1 metrics-server 2 指定內存請求和限制 3 指定 CPU 請求和限制 資源限制 在k8s中對于容器資源限制主要分為以下兩類: 內存資源限制: 內存請求&#xff08;request&#xff09;和內存限制&#xff08;limit&#xff09;分配給一個容器。 我們保障容器擁有它請求數量的…

【云原生】K8S集群

目錄 一、調度約束1.1 POT的創建過程1.1調度過程 二、指定節點調度2.1 通過標簽選擇節點 三、親和性3.1requiredDuringSchedulingIgnoredDuringExecution&#xff1a;硬策略3.1 preferredDuringSchedulingIgnoredDuringExecution&#xff1a;軟策略3.3Pod親和性與反親和性3.4使…

(2)原神角色數據分析-2

功能一&#xff1a; 得到某個屬性的全部角色&#xff0c;將其封裝在class中 """各元素角色信息&#xff1a;一對多""" from pandas import DataFrame, Series import pandas as pd import numpy as npclass FindType:# 自動執行&#xff0c;將…

山東布谷科技直播平臺搭建游戲開發技術分享:數據存儲的重要意義

在市場上的熱門的直播平臺中&#xff0c;有很多小程序為用戶提供各種各樣的功能&#xff0c;這其中就有很多游戲小程序&#xff0c;當今社會獨生子女眾多&#xff0c;很多作為獨生子女的用戶都會去選擇一個能夠社交互動的APP來填補內心的空虛&#xff0c;而直播平臺的實時互動的…

SAP 選擇屏幕組件名描述翻譯時字符長度不夠問題處理

問題&#xff1a;有時候我們在開發report程序的時候&#xff0c;要求程序顯示支持中英文&#xff0c;如果程序是在中文環境下開發的時候&#xff0c;需要進行翻譯處理&#xff0c;但是我們發現選擇屏幕上的組件的描述支持的默認長度是30位&#xff0c;如果超過該如何處理呢 解…