Golang和Rust語言常見功能/庫
時下最流行、最具發展前途的的兩門語言是Golang和Rust。Golang語言簡潔、高效、并發、并且有個強大的囊括了常見功能標準庫。與之相對比,Rust語言則主要是安全、高性能。雖然Rust沒有golang那種"內置電池(Batteries included)"的標準庫,但是Rust的第三方庫(crate,板條箱)極大補充了Rust精煉基本庫的功能。本文我們就介紹一下Golang和Rust常用的庫功能。

參數處理
Golang標準庫中提供了功能。flag庫是非常基礎的功能,在實踐上也非常有用,在做命令行交互程序時候必不可少。
- package main
- import "flag"
- var (
- program = flag.String("p", "", "h program to compile/run")
- outFname = flag.String("o", "", "if specified, write the webassembly binary created by -p here")
- watFname = flag.String("o-wat", "", "if specified, write the uncompiled webassembly created by -p here")
- port = flag.String("port", "", "HTTP port to listen on")
- writeTao = flag.Bool("koan", false, "if true, print the h koan and then exit")
- writeVersion = flag.Bool("v", false, "if true, print the version of h and then exit")
- )
上述代碼會生成一些程序包全局變量,其中將包含命令行參數的值。
在Rust中,常用的命令行解析包是structopt。但是,其工作方式與Golang flag程序包有所不同。structopt會選項加載到結構中,而非全局可變變量。主要因為Rust語言編程實踐中,基本上都會避免全局可變的變量。在大多數情況下,帶有包的全局變量flag是可以的,但前提是必須在程序真正開始執行其需要做的工作之前將它們寫入到程序中。
一個簡單示例源自于pahi庫源碼:
- #[derive(Debug, StructOpt)]
- #[structopt(
- name = "pa'i",
- about = "A WebAssembly runtime in Rust meeting the Olin ABI."
- )]
- struct Opt {
- #[structopt(short, long, default_value = "cranelift")]
- backend: String,
- #[structopt(short, long)]
- function_log: bool,
- #[structopt(short, long)]
- no_cache: bool,
- #[structopt()]
- fname: String,
- #[structopt(short, long, default_value = "_start")]
- entrypoint: String,
- #[structopt()]
- args: Vec<String>,
- }
Rust編譯器會生成所需的參數解析代碼,然后可以使用:
- fn main() {
- let opt = Opt::from_args();
- debug!("args: {:?}", opt.args);
- if opt.backend != "cranelift" {
- return Err(format!(
- "wanted backend to be cranelift, got: {}",
- opt.backend
- ));
- }
錯誤處理
Golang的標準庫具有error接口,可以創建一個描述類型的函數,函數描述為什么功能無法按預期執行,Golang程序必須先做好錯誤處理。比如:
- func Write(w io.Writer, buf []byte) error {
- _, err := w.Write(buf)
- if err != nil {
- log.Println("unable to write:", err)
- return err
- }
- return nil
Rust也具有Error 特性,它使還可以創建一個描述函數為何無法實現其預期功能的類型。這兒我們介紹更加易用的thiserror板條箱構建自定義錯誤類型:
- [dependencies]
- thiserror = "1"
然后,在程序中使用:
- use std::fmt;
- use thiserror::Error;
- #[derive(Debug, Error)]
- struct Divide0;
- impl fmt::Display for Divide0 {
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
- write!(f, "不能被零整除!")
- }
- }
日志記錄
Go標準庫中也自帶了log庫。該庫是一個非常有爭議的記錄器,它的日志記錄缺乏日志記錄級別和上下文感知值之類的功能。
- package main
- import (
- "log"
- )
- func init() {
- log.SetPrefix("TRACE: ")
- log.SetFlags(log.Ldate | log.Lmicroseconds | log.Llongfile)
- }
- func main() {
- log.Println("message")
- log.Fatalln("fatal message")
- log.Panicln("panic message")
- }
- -------------------------------
- TRACE: 2020/09/09 14:24:32.868375 TestLog.go:15: message
- TRACE: 2020/09/09 14:24:32.962329 TestLog.go:18: fatal message
- Process finished with exit code 1
在Rust中,有一個板條箱,這是一個非常簡單的包,它使用error!,warn!,info!,debug!和trace!宏分別關聯到最高和最低日志打印水平。同樣要使用log板條箱,先要增加的項目中,即添加到Cargo.toml的依賴部分。
- [dependencies]
- log = "0.4"
然后,就可以使用了:
- use log::{error, warn, info, debug, trace};
- fn main() {
- trace!("starting main");
- debug!("debug message");
- info!("this is some information");
- warn!("oh no something bad is about to happen");
- error!("oh no it's an error");
- }
注意,默認該庫日志記錄是不會記錄到本地文件中的。要記錄日志還需要其他庫。
pretty_env_logger是和log買一送一,最常搭配使用的板條箱。同樣,先添加項目依賴:
- [dependencies]
- log = "0.4"
- pretty_env_logger = "0.4"
然后,在代碼中使用:
- use log::{error, warn, info, debug, trace};
- fn main() {
- pretty_env_logger::init();
- trace!("這是一個示例程序。");
- debug!("調試信息xxx。");
- info!("程序正在運行中。");
- warn!("[WARN]程序有些參數配置有問題。");
- error!("[ERROR]程序發生嚴重錯誤!");
然后在啟動時候增加,日志級別參數RUST_LOG=trace 運行:
- env RUST_LOG=trace cargo run
- Compiling errex v0.1.0 (/home/lz/test/rust/commoncrate/errex)
- Finished dev [unoptimized + debuginfo] target(s) in 1.32s
- Running `target/debug/errex`
- TRACE errex > 這是一個示例程序。
- DEBUG errex > 調試信息xxx。
- INFO errex > 程序正在運行中。
- WARN errex > [WARN]程序有些參數配置有問題。
- ERROR errex > [ERROR]程序發生嚴重錯誤!

序列化/反序列化
Golang在標準庫內置了 包用來實現JSON編碼/解碼功能。我們可以定義可以輕松讀取和寫入JSON的類型。下面一個例子:
- {
- "id": 3137,
- "author": {
- "id": 420,
- "name": "Chongchong"
- },
- "body": "Hello,This is Chongchong web!",
- "in_reply_to": 3135
- }
在Golang中,可以生成如下的結構體:
- type Author struct {
- ID int `json:"id"`
- Name string `json:"name"`
- }
- type Comment struct {
- ID int `json:"id"`
- Author Author `json:"author"`
- Body string `json:"body"`
- InReplyTo int `json:"in_reply_to"`
- }
Rust沒有開箱即用的功能,需要使用第三方板條箱,最常用的一個庫是serde,可以跨JSON和能想到的所有其他序列化方法使用。
- [dependencies]
- serde = { version = "1", features = ["derive"] }
- serde_json = "1"
注意,上面serde的依賴配置,和其他包有差異。
Golang的JSON包通過使用struct標簽作為元數據來工作,但是Rust沒有,需要改用Rust的衍生功能。
因此,要將serde用于的注釋類型:
- use serde::{Deserialize, Serialize};
- #[derive(Clone, Debug, Deserialize, Serialize)]
- pub struct Author {
- pub id: i32,
- pub name: String,
- }
- #[derive(Clone, Debug, Deserialize, Serialize)]
- pub struct Comment {
- pub id: i32,
- pub author: Author,
- pub body: String,
- pub in_reply_to: i32,
- }
然后,使用以下代碼解析Json:
- fn main() {
- let data = r#"
- {
- "id": 3137,
- "author": {
- "id": 420,
- "name": "Chongchong"
- },
- "body": "Hello,This is Chongchong web!",
- "in_reply_to": 3135
- }
- "#;
- let c: Comment = serde_json::from_str(data).expect("json to parse");
- println!("comment: {:#?}", c);
- }
cargo run
- ...
- Finished dev [unoptimized + debuginfo] target(s) in 0.04s
- Running `target/debug/serdeex`
- comment: Comment {
- id: 3137,
- author: Author {
- id: 420,
- name: "Chongchong",
- },
- body: "Hello,This is Chongchong web!",
- in_reply_to: 3135,
- }

Web開發
在Web開發中HTTP包必不可少的。Golang中可使用net/http充當生產級HTTP客戶端和服務器。
- import (
- "net/http"
- "fmt"
- "log"
- )
- func sayhelloGolang(w http.ResponseWriter, r *http.Request) {
- r.ParseForm()
- fmt.Println("path", r.URL.Path)
- w.Write([]byte("Hello Chongchong!"))
- }
- func main() {
- http.HandleFunc("/",hello)
- err := http.ListenAndServe(":8080", nil)
- if err != nil {
- log.Fatal("ListenAndServe: ", err)
- }
- }
它可以讓我們非常輕松地進行Web開發。Rust標準庫沒有開箱即用的HTTP功能,但是Web的框架也非常豐富。
客戶端
對于HTTP客戶端,可以使用。它還可以與serde無縫集成,以允許從HTTP解析JSON:
- [dependencies]
- reqwest = { version = "0.10", features = ["json"] }
- tokio = { version = "0.2", features = ["full"] }
tokio默認情況下Rust不附帶異步運行時,tokio大約等同于Golang運行時幫助處理的大多數重要事項。簡單實例如下:

運行此命令:
- cargo run
- ...
- Finished dev [unoptimized + debuginfo] target(s) in 3.31s
- Running `target/debug/webcer`
- Status: 200 OK
- Body:
- <!doctype html>
- <html>
- <head>
- <meta charset="utf-8">
- <meta http-equiv="X-UA-Compatible" content="chrome=1">
- <title>hyper.rs | hyper</title>
- <meta name="description" content="">

結合其他功能,reqwest可以做作為一個功能強大的HTTP客戶端。
服務器端
至于HTTP服務器,可以使用warp板條箱。warp是一個建立在Rust的類型系統之上的HTTP服務器框架。
- [dependencies]
- tokio = { version = "0.2", features = ["macros"] }
- warp = "0.2"
讓我們寫個簡單的"Hello,Chongchong"示例:
- use warp::Filter;
- #[tokio::main]
- async fn main() {
- // GET /hello/Chongchong=> 200 OK with body "Hello, Chongchong!"
- let hello = warp::path!("hello" / String)
- .map(|name| format!("Hello, {}!", name));
- warp::serve(hello)
- .run(([127, 0, 0, 1], 3030))
- .await;
- }
然后通過127.0.0.1:3030/hello/Chongchong,就可以提示Hello, Chongchong!。
對 warp應用可以使用其or模式構建多條Web路由:
- let hello = warp::path!("hello" / String)
- .map(|name| format!("Hello, {}!", name));
- let health = warp::path!(".within" / "health")
- .map(|| "OK");
- let routes = hello.or(health);
還可通過過濾器將其他數據類型注入到處理程序中:
- let fact = {
- let facts = pfacts::make();
- warp::any().map(move || facts.clone())
- };
- let fact_handler = warp::get()
- .and(warp::path("fact"))
- .and(fact.clone())
- .and_then(give_fact);
warp是功能強大的HTTP服務器,可以跨生產級Web應用程序所需的所有內容工作。
模版
Web開發中要常用模版來特定化頁面的輸出。Golang的標準庫還包括HTML和純文本模板包html/template和text/template。在Rust中有很多用于HTML模板化的解決方案,比如ructe板條箱。ructe使用Cargo的build.rs功能在編譯時為其模板生成Rust代碼。這樣就可以將HTML模板編譯成結果應用程序二進制文件,從而以驚人的速度呈現它們。
添加Cargo.toml:
- [build-dependencies]
- ructe = { version = "0.12", features = ["warp02"] }
- 還依賴mime板條箱:
- [dependencies]
- mime = "0.3.0"
完成此操作后,templates在當前工作目錄中創建一個新文件夾。創建一個名為的文件hello.rs.html,并將以下內容放入其中:
- @(title: String, message: String)
- <html>
- <head>
- <title>@title</title>
- </head>
- <body>
- <h1>@title</h1>
- <p>@message</p>
- </body>
- </html>
然后使用模板templates.rs:
- use warp::{http::Response, Filter, Rejection, Reply};
- async fn hello_html(message: String) -> Result<impl Reply, Rejection> {
- Response::builder()
- .html(|o| templates::index_html(o, "Hello".to_string(), message).unwrap().clone()))
- }
在src/main.rs底部,通過以下語句引入模版定義:
- include!(concat!(env!("OUT_DIR"), "/templates.rs"));
在main()函數中調用:
- let hello_html_rt = warp::path!("hello" / "html" / String)
- .and_then(hello_html);
- let routes = hello_html_rt.or(health).or(hello);
總結
本文我們介紹了Golang和Rust中常用的功能包括命令行參數解析、錯誤處理、日志、Json處理和Web庫,我們可以發現基本上這些功能都在Golang標準庫中都提供了,而Rust則要引入額外的板條箱,但是借助強大的Cargo包管理工具,其使用也非常便捷。