Rust设计模式:sealed trait续篇

之前写sealed trait时没提他在oauth2-rs中怎么用, 为什么用,这个其实在状态接口设计中很有用,今天展开聊聊。

首先复用上篇的代码,为模拟类库,用mod oauth包起来, 并暴露相关结构体

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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
mod oauth {
use std::marker::PhantomData;

pub struct EndointSet {}
pub struct EndpointNotSet {}

pub trait EndpointState {}

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

pub 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: EndpointState, HasTokenUrl: EndpointState> Client<HasAuthUrl, HasTokenUrl> {
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: EndpointState> Client<EndointSet, HasTokenUrl> {
pub fn get_auth_url(&self) -> &str {
self.auth_url.as_ref().unwrap()
}
}

impl<HasAuthUrl: EndpointState> Client<HasAuthUrl, EndointSet> {
pub fn get_token_url(&self) -> &str {
self.token_url.as_ref().unwrap()
}
}
}

hack

这样的话,其实不设置token_url也可以绕过编译器检查,直接调用get_token_url

比如自己实现一个带有get_token_url方法的HackEndpointSet

1
2
3
4
5
6
7
8
9
10
11
12
13
14
use oauth::*;
struct HackEndpointSet {}
impl EndpointState for HackEndpointSet {}
impl Client<EndointSet, HackEndpointSet> {
pub fn get_token_url(&self) -> &str {
"Hacked!"
}
}
let client =
Client::<EndointSet, HackEndpointSet>::new().set_auth_url("https://auth.example.com");
// .set_token_url("https://token.example.com");

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

防止

怎么防止呢? sealed trait刚好就可以

1
2
3
4
5
6
7
8
9
10
11
mod oauth {
// ...
// sealed trait
mod private {
pub trait EndpointStateSealed {}
}
pub trait EndpointState: private::EndpointStateSealed {}
impl private::EndpointStateSealed for EndointSet {}
impl private::EndpointStateSealed for EndpointNotSet {}
// ...
}

这样的话,HackEndpointSet就没法实现EndpointState trait

报错就会有sealed trait信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
error[E0277]: the trait bound `HackEndpointSet: EndpointStateSealed` is not satisfied
--> src/main.rs:71:28
|
71 | impl EndpointState for HackEndpointSet {}
| ^^^^^^^^^^^^^^^ the trait `EndpointStateSealed` is not implemented for `HackEndpointSet`
|
= help: the following other types implement trait `EndpointStateSealed`:
oauth::EndointSet
EndpointNotSet
note: required by a bound in `oauth::EndpointState`
--> src/main.rs:12:30
|
12 | pub trait EndpointState: private::EndpointStateSealed {}
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `EndpointState`
= note: `EndpointState` is a "sealed trait", because to implement it you also need to implement `oauth::private::EndpointStateSealed`, which is not accessible; this is usually done to force you to use one of the provided types that already implement it
= help: the following types implement the trait:
oauth::EndointSet
oauth::EndpointNotSet

最后附上oauth2-rs相关代码地址, 感兴趣的可以去看下。

如有疑问,请文末留言交流或邮件:newbvirgil@gmail.com 本文链接 : https://newbmiao.github.io/2024/04/25/rust-sealed-trait-2.html