建设银行缴费网站登录,莱芜金点子传媒电子版,西部数码网站开发管理助手,如何通过c语言来做网站随着项目的进展#xff0c;关于Rust的故事又翻开了新的一页#xff0c;今天来到了服务器端的开发场景#xff0c;发现错误处理中的错误类型转换有必要分享一下。 Rust抽象出来了ResultT,E#xff0c;T是返回值的类型#xff0c;E是错误类型。只要函数的返回值的类…随着项目的进展关于Rust的故事又翻开了新的一页今天来到了服务器端的开发场景发现错误处理中的错误类型转换有必要分享一下。 Rust抽象出来了ResultT,ET是返回值的类型E是错误类型。只要函数的返回值的类型被定义为ResutT,E那么作为开发人员就有责任来处理调用这个函数可能发生的错误。通过ResultT,ERust其实给开发人员指明了一条错误处理的道路使代码更加健壮。
场景
服务器端处理api请求的框架Rocket服务器端处理数据持久化的框架tokio_postgres
在api请求的框架中我把返回类型定义成了ResultT, rocket::response::status::Custom\String即错误类型是rocket::response::status::Custom\String。 在tokio_postgres中直接使用tokio_postgres::error::Error。
即如果要处理错误就必须将tokio_postgres::error::Error转换成rocket::response::status::Custom\String。那么我们从下面的原理开始逐一领略Rust的错误处理方式通过对比找到最合适的方式吧。
原理
对错误的处理Rust有3种可选的方式
使用match使用if let使用map_err
下面我结合场景逐一演示各种方式是如何处理错误的。 下面的代码中涉及到2个模块文件。/src/routes/notes.rs是路由层负责将api请求导向合适的service。/src/services/note_books.rs是service层负责业务逻辑和数据持久化的处理。这里的逻辑也很简单就是route层调用service层将数据写入到数据库中。
使用match
src/routes/notes.rs
#[post(/api/notes, format application/json, data note)]
pub async fn post_notes(note: JsonNote) - Result(), rocket::response::status::CustomString {insert_or_update_note(note.into_inner()).await
}/src/services/note_book.rs
pub async fn insert_or_update_note(note: Note,
) - Result(), rocket::response::status::CustomString {let (client, connection) match connect(hostlocalhost dbnamenotes_db userpostgres port5432,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(), note.title, note.content],).await{Ok(res) Ok(()),Err(err) Err(rocket::response::status::Custom(rocket::http::Status::ExpectationFailed,format!({}, err),)),}
}
通过上面的代码我们可以读出一下内容
在service层定义了route层相同的错误类型在service层将持久层的错误转换成了route层的错误类型使用match的代码量还是比较大
使用if let
/src/services/note_book.rs
pub async fn insert_or_update_note(note: Note,
) - Result(), rocket::response::status::CustomString {if let Ok((client, connection)) connect(hostlocalhost dbnamenotes_db userpostgres port5432,NoTls,).await{...if let Ok(res) client.execute(insert into notes (id, title, content) values($1, $2, $3);,[get_system_seconds(), note.title, note.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: JsonNote) - Result(), rocket::response::status::CustomString {insert_or_update_note(note.into_inner()).await
}使用了if let ...代码更加的别扭并且在else分支中拿不到具体的错误信息。
其实不难看出我们的目标是将api的请求经过route层和service层将数据写入到数据中。但这其中的错误处理代码的干扰就特别大甚至要有逻辑嵌套现象。这种代码的已经离初衷比较远了是否有更加简洁的方式使代码能够最大限度的还原逻辑本身把错误处理的噪音降到最低呢 答案肯定是有的。那就是map_err
map_err
map_err是Result上的一个方法专门用于错误的转换。下面的代码经过了map_err的改写看上去是不是清爽了不少啊。 /src/services/note_book.rs
pub async fn insert_or_update_note(note: Note,
) - Result(), rocket::response::status::CustomString {let (client, connection) connect(hostlocalhost dbnamenotes_db userpostgres port5432,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(), note.title, note.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: JsonNote) - Result(), rocket::response::status::CustomString {insert_or_update_note(note.into_inner()).await
}经过map_err改写后的代码代码的逻辑流程基本上还原了逻辑本身但是map_err要额外占4行代码且错误对象的初始化代码存在重复。在实际的工程项目中service层的处理函数可能是成百上千如果再乘以4那多出来的代码量也不少啊这会给后期的维护带来不小的压力。
那是否还有改进的空间呢答案是Yes。 Rust为我们提供了FromT trait用于类型转换。它定义了从一种类型T到另一种类型Self的转换方法。我觉得这是Rust语言设计亮点之一。 但是Rust有一个显示即实现FromT trait的结构必须有一个在当前的crate中也就是说我们不能直接通过FromT来实现从tokio_postgres::error::Error到rocket::response::status::CustomString。也就是说下面的代码编译器会报错。
impl Fromtokio_postgres::Error for rocket::response::status::CustomString {}报错如下
32 | impl Fromtokio_postgres::Error for rocket::response::status::CustomString {}| ^^^^^---------------------------^^^^^----------------------------------------| | | || | | rocket::response::status::Custom is not defined in the current crate| | tokio_postgres::Error is not defined in the current crate| impl doesnt use only types from inside the current crate因此我们要定义一个类型MyError作为中间类型来转换一下。 /src/models.rs
pub struct MyError {pub message: String,
}
impl Fromtokio_postgres::Error for MyError {fn from(err: Error) - Self {Self {message: format!({}, err),}}
}
impl FromMyError for rocket::response::status::CustomString {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::CustomString {let (client, connection) connect(hostlocalhost dbnamenotes_db userpostgres port5432,NoTls,).await.map_err(MyError::from)?;...let _ client.execute(insert into notes (id, title, content) values($1, $2, $3);,[get_system_seconds(), note.title, note.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: JsonNote) - Result(), rocket::response::status::CustomString {insert_or_update_note(note.into_inner()).await
}而MyError到rocket::response::status::CustomString之间的转换是隐式的由编译器来完成。因此我们的错误类型的转换最终缩短为map_err(|err|MyError::from(err))再简写为map_err(MyError::from)。
关于错误处理中的类型转换应用解析就到这里。通过分析这个过程我们可以看到在设计模块时我们应该确定一种错误类型就像tokio_postgres库一样只暴露了tokio_postgress::error::Error一种错误类型。这种设计既方便我们在设计模块时处理错误转换也方便其我们的模块在被调用时其它代码进行错误处理。