Rust中類型轉(zhuǎn)換在錯(cuò)誤處理中的應(yīng)用小結(jié)
隨著項(xiàng)目的進(jìn)展,關(guān)于Rust的故事又翻開了新的一頁,今天來到了服務(wù)器端的開發(fā)場(chǎng)景,發(fā)現(xiàn)錯(cuò)誤處理中的錯(cuò)誤類型轉(zhuǎn)換有必要分享一下。
Rust抽象出來了Result<T,E>,T是返回值的類型,E是錯(cuò)誤類型。只要函數(shù)的返回值的類型被定義為Resut<T,E>,那么作為開發(fā)人員就有責(zé)任來處理調(diào)用這個(gè)函數(shù)可能發(fā)生的錯(cuò)誤。通過Result<T,E>,Rust其實(shí)給開發(fā)人員指明了一條錯(cuò)誤處理的道路,使代碼更加健壯。
場(chǎng)景
- 服務(wù)器端處理api請(qǐng)求的框架:Rocket
- 服務(wù)器端處理數(shù)據(jù)持久化的框架:tokio_postgres
在api請(qǐng)求的框架中,我把返回類型定義成了 Result<T, rocket::response::status::Custom\<String>> ,即錯(cuò)誤類型是 rocket::response::status::Custom\<String> 。
在tokio_postgres中,直接使用 tokio_postgres::error::Error 。
即如果要處理錯(cuò)誤,就必須將 tokio_postgres::error::Error 轉(zhuǎn)換成 rocket::response::status::Custom\<String> 。那么我們從下面的原理開始,逐一領(lǐng)略Rust的錯(cuò)誤處理方式,通過對(duì)比找到最合適的方式吧。
原理
對(duì)錯(cuò)誤的處理,Rust有3種可選的方式
- 使用match
- 使用if let
- 使用map_err
下面我結(jié)合場(chǎng)景,逐一演示各種方式是如何處理錯(cuò)誤的。
下面的代碼中涉及到2個(gè)模塊(文件)。 /src/routes/notes.rs 是路由層,負(fù)責(zé)將api請(qǐng)求導(dǎo)向合適的service。 /src/services/note_books.rs 是service層,負(fù)責(zé)業(yè)務(wù)邏輯和數(shù)據(jù)持久化的處理。這里的邏輯也很簡單,就是route層調(diào)用service層,將數(shù)據(jù)寫入到數(shù)據(jù)庫中。
使用match
src/routes/notes.rs
#[post("/api/notes", format = "application/json", data = "<note>")]
pub async fn post_notes(note: Json<Note>) -> Result<(), rocket::response::status::Custom<String>> {
insert_or_update_note(¬e.into_inner()).await
}/src/services/note_book.rs
pub async fn insert_or_update_note(
note: &Note,
) -> Result<(), rocket::response::status::Custom<String>> {
let (client, connection) = match connect(
"host=localhost dbname=notes_db user=postgres port=5432",
NoTls,
)
.await
{
Ok(res) => res,
Err(err) => {
return Err(rocket::response::status::Custom(
rocket::http::Status::ExpectationFailed,
format!("{}", err),
));
}
};
...
match client
.execute(
"insert into notes (id, title, content) values($1, $2, $3);",
&[&get_system_seconds(), ¬e.title, ¬e.content],
)
.await
{
Ok(res) => Ok(()),
Err(err) => Err(rocket::response::status::Custom(
rocket::http::Status::ExpectationFailed,
format!("{}", err),
)),
}
}通過上面的代碼我們可以讀出一下內(nèi)容:
- 在service層定義了route層相同的錯(cuò)誤類型
- 在service層將持久層的錯(cuò)誤轉(zhuǎn)換成了route層的錯(cuò)誤類型
- 使用match的代碼量還是比較大
使用if let
/src/services/note_book.rs
pub async fn insert_or_update_note(
note: &Note,
) -> Result<(), rocket::response::status::Custom<String>> {
if let Ok((client, connection)) = connect(
"host=localhost dbname=notes_db user=postgres port=5432",
NoTls,
)
.await
{
...
if let Ok(res) = client
.execute(
"insert into notes (id, title, content) values($1, $2, $3);",
&[&get_system_seconds(), ¬e.title, ¬e.content],
)
.await
{
Ok(())
} else {
Err(rocket::response::status::Custom(
rocket::http::Status::ExpectationFailed,
format!("{}", "unknown error"),
))
}
} else {
Err(rocket::response::status::Custom(
rocket::http::Status::ExpectationFailed,
format!("{}", "unknown error"),
))
}
}src/routes/notes.rs
#[post("/api/notes", format = "application/json", data = "<note>")]
pub async fn post_notes(note: Json<Note>) -> Result<(), rocket::response::status::Custom<String>> {
insert_or_update_note(¬e.into_inner()).await
}使用了 if let ... ,代碼更加的別扭,并且在else分支中,拿不到具體的錯(cuò)誤信息。
其實(shí),不難看出,我們的目標(biāo)是將api的請(qǐng)求,經(jīng)過route層和service層,將數(shù)據(jù)寫入到數(shù)據(jù)中。但這其中的錯(cuò)誤處理代碼的干擾就特別大,甚至要有邏輯嵌套現(xiàn)象。這種代碼的已經(jīng)離初衷比較遠(yuǎn)了,是否有更加簡潔的方式,使代碼能夠最大限度的還原邏輯本身,把錯(cuò)誤處理的噪音降到最低呢?答案肯定是有的。那就是map_err
map_err
map_err是Result上的一個(gè)方法,專門用于錯(cuò)誤的轉(zhuǎn)換。下面的代碼經(jīng)過了map_err的改寫,看上去是不是清爽了不少啊。/src/services/note_book.rs
pub async fn insert_or_update_note(
note: &Note,
) -> Result<(), rocket::response::status::Custom<String>> {
let (client, connection) = connect(
"host=localhost dbname=notes_db user=postgres port=5432",
NoTls,
)
.await
.map_err(|err| {
rocket::response::status::Custom(
rocket::http::Status::ExpectationFailed,
format!("{}", err),
)
})?;
...
let _ = client
.execute(
"insert into notes (id, title, content) values($1, $2, $3);",
&[&get_system_seconds(), ¬e.title, ¬e.content],
)
.await
.map_err(|err| {
rocket::response::status::Custom(
rocket::http::Status::ExpectationFailed,
format!("{}", err),
)
})?;
Ok(())
}src/routes/notes.rs
#[post("/api/notes", format = "application/json", data = "<note>")]
pub async fn post_notes(note: Json<Note>) -> Result<(), rocket::response::status::Custom<String>> {
insert_or_update_note(¬e.into_inner()).await
}經(jīng)過map_err改寫后的代碼,代碼的邏輯流程基本上還原了邏輯本身,但是map_err要額外占4行代碼,且錯(cuò)誤對(duì)象的初始化代碼存在重復(fù)。在實(shí)際的工程項(xiàng)目中,service層的處理函數(shù)可能是成百上千,如果再乘以4,那多出來的代碼量也不少啊,這會(huì)給后期的維護(hù)帶來不小的壓力。
那是否還有改進(jìn)的空間呢?答案是Yes。
Rust為我們提供了From<T> trait,用于類型轉(zhuǎn)換。它定義了從一種類型T到另一種類型Self的轉(zhuǎn)換方法。我覺得這是Rust語言設(shè)計(jì)亮點(diǎn)之一。
但是,Rust有一個(gè)顯示,即實(shí)現(xiàn)From<T> trait的結(jié)構(gòu),必須有一個(gè)在當(dāng)前的crate中,也就是說我們不能直接通過From<T>來實(shí)現(xiàn)從 tokio_postgres::error::Error 到 rocket::response::status::Custom<String> 。也就是說下面的代碼編譯器會(huì)報(bào)錯(cuò)。
impl From<tokio_postgres::Error> for rocket::response::status::Custom<String> {}報(bào)錯(cuò)如下:
32 | impl From<tokio_postgres::Error> for rocket::response::status::Custom<String> {}
| ^^^^^---------------------------^^^^^----------------------------------------
| | | |
| | | `rocket::response::status::Custom` is not defined in the current crate
| | `tokio_postgres::Error` is not defined in the current crate
| impl doesn't use only types from inside the current crate
因此,我們要定義一個(gè)類型 MyError 作為中間類型來轉(zhuǎn)換一下。/src/models.rs
pub struct MyError {
pub message: String,
}
impl From<tokio_postgres::Error> for MyError {
fn from(err: Error) -> Self {
Self {
message: format!("{}", err),
}
}
}
impl From<MyError> for rocket::response::status::Custom<String> {
fn from(val: MyError) -> Self {
status::Custom(Status::ExpectationFailed, val.message)
}
}/src/services/note_book.rs
pub async fn insert_or_update_note(
note: &Note,
) -> Result<(), rocket::response::status::Custom<String>> {
let (client, connection) = connect(
"host=localhost dbname=notes_db user=postgres port=5432",
NoTls,
)
.await
.map_err(MyError::from)?;
...
let _ = client
.execute(
"insert into notes (id, title, content) values($1, $2, $3);",
&[&get_system_seconds(), ¬e.title, ¬e.content],
)
.await
.map_err(MyError::from)?;
Ok(())
}src/routes/notes.rs
#[post("/api/notes", format = "application/json", data = "<note>")]
pub async fn post_notes(note: Json<Note>) -> Result<(), rocket::response::status::Custom<String>> {
insert_or_update_note(¬e.into_inner()).await
}而 MyError 到 rocket::response::status::Custom<String> 之間的轉(zhuǎn)換是隱式的,由編譯器來完成。因此我們的錯(cuò)誤類型的轉(zhuǎn)換最終縮短為 map_err(|err|MyError::from(err)) ,再簡寫為 map_err(MyError::from)。
關(guān)于錯(cuò)誤處理中的類型轉(zhuǎn)換應(yīng)用解析就到這里。通過分析這個(gè)過程,我們可以看到,在設(shè)計(jì)模塊時(shí),我們應(yīng)該確定一種錯(cuò)誤類型,就像tokio_postgres庫一樣,只暴露了tokio_postgress::error::Error一種錯(cuò)誤類型。這種設(shè)計(jì)既方便我們?cè)谠O(shè)計(jì)模塊時(shí)處理錯(cuò)誤轉(zhuǎn)換,也方便其我們的模塊在被調(diào)用時(shí),其它代碼進(jìn)行錯(cuò)誤處理。
到此這篇關(guān)于Rust中類型轉(zhuǎn)換在錯(cuò)誤處理中的應(yīng)用解析的文章就介紹到這了,更多相關(guān)Rust錯(cuò)誤處理內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Rust使用csv crate構(gòu)建CSV文件讀取器的全過程
這篇文章主要學(xué)習(xí)如何基于Rust使用csv這個(gè)crate構(gòu)建一個(gè)CSV文件讀取器的過程,學(xué)習(xí)了csv相關(guān)的用法以及一些往期學(xué)過的crate的復(fù)習(xí),兼顧了實(shí)用性和Rust的學(xué)習(xí),需要的朋友可以參考下2024-05-05
Rust重載運(yùn)算符之復(fù)數(shù)四則運(yùn)算的實(shí)現(xiàn)
這篇文章主要為大家詳細(xì)介紹了Rust如何實(shí)現(xiàn)復(fù)數(shù)以及復(fù)數(shù)的四則運(yùn)算,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2023-08-08
關(guān)于Rust命令行參數(shù)解析以minigrep為例
本文介紹了如何使用Rust的std::env::args函數(shù)來解析命令行參數(shù),并展示了如何將這些參數(shù)存儲(chǔ)在變量中,隨后,提到了處理文件和搜索邏輯的步驟,包括讀取文件內(nèi)容、搜索匹配項(xiàng)和輸出搜索結(jié)果,最后,總結(jié)了Rust標(biāo)準(zhǔn)庫在命令行參數(shù)處理中的便捷性和社區(qū)資源的支持2025-02-02
Rust 中引用模式與值模式的對(duì)比實(shí)踐指南
文章詳細(xì)介紹了Rust中的引用模式和值模式的區(qū)別,包括它們?cè)谒袡?quán)、生命周期、性能、內(nèi)存影響以及實(shí)際應(yīng)用中的選擇建議,通過對(duì)比和實(shí)際代碼示例,幫助讀者理解如何根據(jù)具體需求選擇合適的模式,從而寫出高效且正確的Rust代碼,感興趣的朋友跟隨小編一起看看吧2025-11-11
Rust?實(shí)現(xiàn)?async/await的詳細(xì)代碼
異步編程在 Rust 中的地位非常高,很多 crate 尤其是多IO操作的都使用了 async/await,這篇文章主要介紹了Rust?如何實(shí)現(xiàn)?async/await,需要的朋友可以參考下2022-09-09

