rust 链条式调用

53 阅读2分钟

作为一种编译式语言,在代码组织层面怎么做到既流畅又优雅呢? 比如像下面这种写法参考 sqlx 链接池的配置

let conn = MySqlPoolOptions::new()
        // max connections
        .max_connections(cfg.max_conn)
        //
        .max_lifetime(Duration::from_secs(59))
        // client connect max idle
        .idle_timeout(Duration::from_secs(60))
        // connect db
        .connect(&cfg.dsn)
        .await
        .with_context(|| format!("connect db {}", cfg.dsn))?;

分析 max_conn** 方法 类似于 接受参数为 self 并且返回一个 Self

pub fn idle_timeout(mut self, timeout: impl Into<Option<Duration>>) -> Self {
    self.idle_timeout = timeout.into();
    self
}

照猫画虎,在 http 短链服务中,我们都需要确定一个固定的基本的结构,然后加上可变的部分, 比如

{
    "err_no": 10000,
    "err_msg": "success",
    "data": {
    /// many key and values
    }
}

根据以上需求, 我们很容易确定

#[derive(Debug)]
pub struct AppErr<T: serde::Serialize> {
	 err_no: i64,
	 err_msg: String,
	 data: Option<T>,
}

但是,有一个问题,因为在编写新构造 AppErr 对象的时候就要确定 泛型参数T的类型, 但是在很多情况下,我们只是 data 字段的值和类型不一样, 这个时候,为了减少重复代码的编写, 我们需要增加

impl<T: Serialize> AppErr<T> {
	pub fn new(err_no: i64, err_msg: &str) -> Self {
		let data: Option<T> = None;
		Self { err_msg: err_msg.to_owned(), err_no, data }
	}

	pub fn with_data<F: serde::Serialize>(self, data: Option<F>) -> AppErr<F> {
		AppErr { data, err_no: self.err_no, err_msg: self.err_msg }
	}
}

with_data 方法返回一个新类型的 AppErr 对象,并且 data 字段完全有可能不同于前一个 data 的类型。

完整的代码如下:

#[derive(Debug)]
pub struct AppErr<T: serde::Serialize> {
	 err_no: i64,
	 err_msg: String,
	 data: Option<T>,
}

impl<T: Serialize> AppErr<T> {
	pub fn new(err_no: i64, err_msg: &str) -> Self {
		let data: Option<T> = None;
		Self { err_msg: err_msg.to_owned(), err_no, data }
	}

	pub fn with_data<F: serde::Serialize>(self, data: Option<F>) -> AppErr<F> {
		AppErr { data, err_no: self.err_no, err_msg: self.err_msg }
	}

	pub fn with_err_no(self, err_no: i64) -> Self {
		Self { err_no, ..self }
	}

	pub fn with_err_msg(self, err_msg: &str) -> Self {
		Self { err_msg: err_msg.to_owned(), ..self }
	}
}
#[cfg(test)]
mod tests {
	use super::*;

	#[test]
	fn it_works() {
		let data = AppErr::<i32>::new(1000, "foo/baz").with_data(Some("hello"));
		println!("data {:?}", data);
		// let data = data.with_err_no(999);
		// println!("data {data:?}",);
		// let data = data.with_err_msg("test/case");
		// println!("data {data:?}",);
		let data = data.with_data(Some(233));
		println!("data {data:?}",);
	}
}