定制网站【一起学Rust | 设计模式】新类型模式

文章目录


前言

定制网站新的类型模式提供封装定制网站以及保证在编译时提供定制网站正确类型的值。定制网站定制网站新类型模式有多种用途和好处,定制网站比如可以处理不同的crate定制网站中的和特质的关系。定制网站本期我们将一起探讨一下Rust定制网站设计模式中的新类型模式。

定制网站本期内容是学习设计模式笔记


一、新类型模式

定制网站如果在某些情况下,定制网站我们希望一个类型的行定制网站为类似于另一种类型,定制网站或者在编译时强制执行某些行为,定制网站而实现这些仅使用类型定制网站别名是不够的。
例如,定制网站出于安全考虑 ,定制网站我们想要为String创建自定义实现。对于这种情况,我们可以使用该Newtype模式来提供安全类型封装

1. 新类型模式的实现

使用单个字段的元组来实现包装一个类型,使之称为一个新类型,而不是那个类型的别名,这样就可以拓展这个类型。

2. 官方例子

下面这段代码来自《Rust》,只是伪代码,并未实现具体功能,但是描述了新类型模式的思想:使用元组来包装一个新类型,通过拓展这个新类型,来拓展原本类型的功能。Foo是一个基础类型,他有他本身的实现方法,Bar包装自Foo,它除了有Foo的特性,还为其实现了新的方法,通过测试,这两种类型,已然变成了两种独立的类型。

// 一个类型,可以是自己包里面的,也可以是别的包里面的。struct Foo {    //..}impl Foo {    // Foo 类型的实现,这些实现在Bar里面是没有的,Bar是下面的类型。    //..}// 一个新类型,它包装自Foopub struct Bar(Foo);impl Bar {    // 构造函数.    pub fn new(        //..    ) -> Self {        //..    }    //..}fn main() {    let b = Bar::new(...);    // Foo和Bar类型不兼容,以下不进行类型检查。    // let f: Foo = b;    // let b: Bar = Foo { ... };}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33

3. 使用动机

新类型的主要动机是抽象。它允许在精确控制接口的同时在类型之间共享实现细节。通过使用新类型而不是将实现类型作为 API 的一部分公开,它允许您向后兼容地更改实现。

这样就是区分同一个类型不同含义,就比如f64类型,可以定义成米,或者千米等不同的类型。

4. 优点

新类型是一种零成本的抽象——没有运行时开销。

新类型与包装类型是互不兼容的,因此用户不会混淆两种类型。

Rust的隐私系统确保用户不会访问到包装了的类型,他的字段默认是私有的。

5. 缺点

没有语言支持,因此包装类型的每一个方法都必须写一个传递方法,来使用包装类型的方法。并且要为每一个实现了的特质来写传递方法。这样就显得极为复杂。

二、应用

1.标识符分离

假设我们有一个用户id,他是无符号整数类型的usize。我们系统对用户操作都是通过这个用户id来实现的。就比如我们有这么一个方法:

fn get_user_id_from_username(username: &str) -> usize
  • 1

如果我们还要对用户的帖子进行操作,那么代码应该这么写

let user_id: usize = get_user_id_from_username(username);let post_id: usize = get_last_post();fn delete_post(post_id: usize) {	// ...}delete_post(user_id);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

此时我们使用delete_post删除用户帖子,但是不小心传入了用户id,这就很麻烦了,这样辨识度就很不好,为了提高辨识度,我们使用新类型模式来区分两个类型:

struct UserId(pub usize);
  • 1

然后让get_user_id_from_username返回该类型,

fn get_user_id_from_username(username: String) -> UserId {	let user_id: usize = ...	UserId(user_id)}
  • 1
  • 2
  • 3
  • 4

这里做了如下改动
这样在我们写错代码的时候就会这么提示了


新类型模式在编译时强制执行类型安全,而在运行时没有任何性能开销。

2.为新类型添加功能

现在有如下需求,需要为用户设置个“禁止登录”的用户列表。考虑使用HashSet实现,我们定义的代码如下

let banned_users: HashSet<UserId> = HashSet::new();
  • 1

但是光这一点是无法编译的,我们的UserId并没有想等,哈希等行为。我们可以使用内置派生宏来实现这些行为,会自动基于我们的结构体来生成这些实现,代码如下,

#[derive(PartialEq, Eq, Hash)]struct UserId(usize);
  • 1
  • 2

3. 限制类型内容

有时候我们需要对用户名进行校验,比如我们需要用户名全部都是由小写字母组成的。现在我们来将String类型来定义成一个新类型,Username

struct Username(String);
  • 1

然后创建个创建用户名的唯一方法,我们使用TryFrom特质

impl TryFrom<String> for Username {	type Error = String;	fn try_from(value: String) -> Result<Self, Self::Error> {		if value.chars().all([object Object]c[object Object] matches!(c, 'a'..='z')) {			Ok(Username(value))		} else {			Err(value)		}	}}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

这里重写了try_from行为,在类型转换时就已经对username进行了检测构造符合条件的用户名

4. 处理包之间特质和结构体的关系

在使用外部特质时我们可能会遇到以下问题

在我们的crate中使用MyTrait时,编译器就不知道我们用的是crate3中的MyTrait还是crate4中的MyTrait。Rust有一套《孤儿规则》专门来处理这种情况,我们会在后期的文章中说明。

现在我们使用新类型模式来实现外来结构体特质,或者拓展特质。

在某crate包中有如下特质

trait ToTree {   // ...}fn very_useful_function(something: impl ToTree) -> () {    // ..}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

在我们的crate中这么写

struct Wrapper(pub crate_y[object Object]MyType);impl ToTree for Wrapper {	// ...}// 使用very_useful_function(Wrapper(foreign_value))
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

总结

本期介绍了Rust设计模式中的新类型模式,并且指明了该设计模式的使用场景,其优点与缺点。并且通过一个实例来应用新类型模式,拓展包装类型的行为和特质,从而实现处理包与包之间结构体和特质的关系,限制类型内容等操作。

网站建设定制开发 软件系统开发定制 定制软件开发 软件开发定制 定制app开发 app开发定制 app开发定制公司 电商商城定制开发 定制小程序开发 定制开发小程序 客户管理系统开发定制 定制网站 定制开发 crm开发定制 开发公司 小程序开发定制 定制软件 收款定制开发 企业网站定制开发 定制化开发 android系统定制开发 定制小程序开发费用 定制设计 专注app软件定制开发 软件开发定制定制 知名网站建设定制 软件定制开发供应商 应用系统定制开发 软件系统定制开发 企业管理系统定制开发 系统定制开发