Rust-类型转换进阶

这篇文章收录于Rust 实战专栏。这个专栏中的相关代码来自于我开发的笔记系统。它启动于是2023年的9月14日。相关技术栈目前包括:Rust,Javascript。关注我,我会通过这个项目的开发给大家带来相关实战技术的分享。


关于Rust的类型转换,我在之前的已经感受到了Rust类型的一等公民地位中做了阐述。随着项目进度的推进,对类型转换的使用场景也变得丰富起来。

场景

说到数据转换,在程序开发中是一件很平常的事情,比如,我们下面要讨论的场景

models::User定义如下:

rust 复制代码
#[derive(Debug, Deserialize, Serialize)]
pub struct User {
    pub id: String,
    pub user_name: String,
    pub gender: Gender,
    pub alias: Option<String>,
    pub birthday_year: Option<u8>,
    pub birthday_month: Option<u8>,
    pub birthday_day: Option<u8>,
}

在数据集中,gender的类型是postgres的int not nulltokio_posgres库会将其转换成Rust的i32类型。birthday_year的类型是postgres的int nulltokio_postgres库会自动将其转换成Rust的Option<i32>类型。

因此,我们的目标是

  1. i32转换成models::Gender
  2. Option<i32>转换成Option<u8>

先睹为快,最终的转换应用代码如下

rust 复制代码
    let row= client.query_one("select id, user_name, alias, gender, birthday_year, birthday_month, birthday_day from users where id=$1", &[id]).await.map_err(MyError::from)?;
    Ok(User {
        ...
        gender: SqlGender(row.get("gender")).into(),
        birthday_year: Sqlu8(row.get("birthday_year")).into(),
    })

实现步骤

i32 to models::Sex

在这个转换过程中,我们会使用到std::convert::From<T> trait

  1. 声明一个临时类型SqlSex
rust 复制代码
pub struct SqlGender(pub i32);

之所以要声明这个临时类型,是因为我们要告诉编译器这个tuple接收的数据类型是i32。上面的row.get("gender")的返回实现了FromSql的trait。tokio_postgres通过FromSql实现了对i32的转换。

  1. models::Gender上实现From<SqlGender>
rust 复制代码
impl From<SqlGender> for Gender {
    fn from(val: SqlGender) -> Self {
        if let Ok(val1) = u8::try_from(val.0) {
            if let Ok(result) = Gender::try_from(val1) {
                return result;
            }
        }
        Sex::NotSet
    }
}

上面的代码,实际上先将i32数据类型转换成u8类型,然后再将u8类型转换成Gender。上面的两个转换过程我们都使用了try_fromtry_from来源于TryFrom trait。其实,如果我们看Rust关于u8的文档,会看见一串From<T>TryFrom<T>的实现。

Option<i32> to Option<u8>

和上面的转不同,这个转换是结果是Option,但由于输入的数据也是一个Option,因此,这里我们还是使用的From<T>。如果转换可能存在失败的情况,且我们要处理失败,那么我们应该使用std::convert::TryFrom<T>

  1. 声明临时类型Sqlu8
rust 复制代码
pub struct Sqlu8(pub Option<i32>);

声明这个临时类型和上面的原因是一样的,告诉编译器这里使用的是Option<i32>类型。

  1. Option<u8>上实现From<Sqlu8>
rust 复制代码
impl From<Sqlu8> for Option<u8> {
    fn from(val: Sqlu8) -> Self {
        if let Some(val1) = val.0 {
            if let Ok(result) = u8::try_from(val1) {
                return Some(result);
            }
        }
        None
    }
}

上面的代码先拿到有效的i32数据,然后再将i32转换成u8类型,任何失败都将返回None

转换的应用

实现了上面的步骤,我们通过下面的方式来使用转换。

rust 复制代码
    let row= client.query_one("select id, user_name, alias, gender, birthday_year, birthday_month, birthday_day from users where id=$1", &[id]).await.map_err(MyError::from)?;
    Ok(User {
        ...
        gender: SqlGender(row.get("gender")).into(),
        birthday_year: Sqlu8(row.get("birthday_year")).into(),
    })

我们先用声明的临时类型来包裹数据,然后通过调用.into()来实现转换。

Rust本身也有类似的用法,例如将字符串切片转换成String类型。

rust 复制代码
let msg :String = "hello".into();

这里看起来有没有一点魔幻的感觉,反正我是有的。实现转换的代码和应用转换的代码感觉没啥关联。我查了一下,这是Rust的"关注点分离"设计模式的一种体现。这样设计到好处显而易见,即我们可以无限扩展其类型的转换而不会对已有的代码造成任何影响。例如,我们上面就对i32, u8的转换进行了相关的扩展。

关于临时类型

我们在这里使用临时类型的原因是要告诉编译器,以pub struct Sqlu8(pub Option<i32>);为例,我们要接受一个类型为Option<i32>的值。即相当于一个中间变量。

还有一种情况需要使用临时类型,即如果你要转换的两个类型,都是从第三方模块中引入的,这个时候也需要加入一个临时类型过度。因为Rust不允许转换的两个类型都不在当前模块内。

小结

我们描述了转换的场景,具体的转换步骤和转换的应用方式。这里的场景是数据集和本地类型之间的数据转换。类似的场景还有很多,只要涉及到不同的上下文,数据转换的需求就会出现。使用Rust的std::convert::From<T> trait,实际上就是在实践"关注点分离"的设计模式,它会大大提升我们代码的可维护性和可扩展性,个人认为这是写好Rust代码的重要方法之一。

如有问题,欢迎大家留言交流。关注我,后面会给大家带来更多关于Rust开发实战技术的分享。

相关推荐
Cachel wood5 分钟前
python round四舍五入和decimal库精确四舍五入
java·linux·前端·数据库·vue.js·python·前端框架
学代码的小前端7 分钟前
0基础学前端-----CSS DAY9
前端·css
joan_8511 分钟前
layui表格templet图片渲染--模板字符串和字符串拼接
前端·javascript·layui
m0_7482361142 分钟前
Calcite Web 项目常见问题解决方案
开发语言·前端·rust
Watermelo6171 小时前
详解js柯里化原理及用法,探究柯里化在Redux Selector 的场景模拟、构建复杂的数据流管道、优化深度嵌套函数中的精妙应用
开发语言·前端·javascript·算法·数据挖掘·数据分析·ecmascript
m0_748248941 小时前
HTML5系列(11)-- Web 无障碍开发指南
前端·html·html5
m0_748235611 小时前
从零开始学前端之HTML(三)
前端·html
一个处女座的程序猿O(∩_∩)O3 小时前
小型 Vue 项目,该不该用 Pinia 、Vuex呢?
前端·javascript·vue.js
hackeroink6 小时前
【2024版】最新推荐好用的XSS漏洞扫描利用工具_xss扫描工具
前端·xss
迷雾漫步者8 小时前
Flutter组件————FloatingActionButton
前端·flutter·dart