前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Rust设计模式:typestate

Rust设计模式:typestate

作者头像
newbmiao
发布2024-04-28 10:07:24
840
发布2024-04-28 10:07:24
举报
文章被收录于专栏:学点Rust学点Rust

今天聊聊typestate模式。

新版oauth2-rs使用这个模式是想解决一个问题。

Oauth2中,一般需要提前配置好auth_url, token_url, introspect_url等,才能调用后续的鉴权相关功能。比如没有token_url配置好,你就无法成功拿授权码(code)获取token

但如果用户忘了配置,那就会在调用获取token时才得到运行时错误。

能不能在编译时就把错误暴露出来,让用户提前感知问题?

这就是typestate模式能很好解决的问题。

文章目录

  • typestate
  • 更进一步
  • 彩蛋

typestate

typestate是将状态定义到类型中,这样对于强类型约束的语言,可以很好的利用编译检查是否有状态和调用不符合的情况

比如如下两个ticket结构体表示不同状态的ticket

EmptyTicket只能new或者book

book会转化为BookedTicket, 这时只能use消费掉或cancel释放为EmptyTicket

代码语言:javascript
复制
struct EmptyTicket {}
struct BookedTicket {}

impl EmptyTicket {
    fn new() -> Self {
        EmptyTicket {}
    }
    fn book(self) -> BookedTicket {
        BookedTicket {}
    }
}

impl BookedTicket {
    fn use(self) {}
    fn cancel(self) -> EmptyTicket {
        EmptyTicket {}
    }
}

更进一步

Oauth-rs如何解决文首提到的问题呢?

下边用一段代码模拟一下:

auth_urltoken_url都是EndpointState trait

初始是未设置,用EndointNotSet表示状态

当其中一个设置时,返回的Client就会更新对应的状态类型为EndointSet

这样后续相应的方法基于拥有此类型的Client, 就能保证必须先设置url才能调用相应的方法

比如

Client::<EndointSet, EndpointNotSet>就声明了这个Client只能调用auth_url相关方法,不能调用token_url相关方法, 因为类型的状态上明确了token_url没有设置。

具体代码如下:

代码语言:javascript
复制
use std::marker::PhantomData;

struct EndointSet {}
struct EndpointNotSet {}

trait EndpointState {}

impl EndpointState for EndointSet {}
impl EndpointState for EndpointNotSet {}

struct Client<HasAuthUrl = EndpointNotSet, HasTokenUrl = EndpointNotSet>
where
    HasAuthUrl: EndpointState,
    HasTokenUrl: EndpointState,
{
    auth_url: Option<String>,
    token_url: Option<String>,
    phantom: std::marker::PhantomData<(HasAuthUrl, HasTokenUrl)>,
}

impl<HasAuthUrl, HasTokenUrl> Client<HasAuthUrl, HasTokenUrl>
where
    HasAuthUrl: EndpointState,
    HasTokenUrl: EndpointState,
{
    pub fn new() -> Self {
        Client {
            auth_url: None,
            token_url: None,
            phantom: PhantomData,
        }
    }

    pub fn set_auth_url(self, auth_url: &str) -> Client<EndointSet, HasTokenUrl> {
        Client {
            auth_url: Some(auth_url.to_string()),
            token_url: self.token_url,
            phantom: PhantomData,
        }
    }

    pub fn set_token_url(self, token_url: &str) -> Client<HasAuthUrl, EndointSet> {
        Client {
            auth_url: self.auth_url,
            token_url: Some(token_url.to_string()),
            phantom: PhantomData,
        }
    }
}

impl<HasTokenUrl> Client<EndointSet, HasTokenUrl>
where
    HasTokenUrl: EndpointState,
{
    pub fn get_auth_url(&self) -> &str {
        self.auth_url.as_ref().unwrap()
    }
}

impl<HasAuthUrl> Client<HasAuthUrl, EndointSet>
where
    HasAuthUrl: EndpointState,
{
    pub fn get_token_url(&self) -> &str {
        self.token_url.as_ref().unwrap()
    }
}
fn main() {
    let client =
        Client::<EndointSet, EndpointNotSet>::new().set_auth_url("https://auth.example.com");
    // .set_token_url("https://token.example.com");
    // comment这里如果取消会报错,因为和set_token_url所需要的Client的类型声明不符

    println!("Auth URL: {}", client.get_auth_url());
    println!("Token URL: {}", client.get_token_url());
}

彩蛋

这个pattern其实是 2019 年就提出来的,很感兴趣可以看看这篇博客[1]

参考资料

[1]

博客: https://cliffle.com/blog/rust-typestate

本文参与?腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2024-04-24,如有侵权请联系?cloudcommunity@tencent.com 删除

本文分享自 菜鸟Miao 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与?腾讯云自媒体分享计划? ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • typestate
  • 更进一步
  • 彩蛋
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
http://www.vxiaotou.com