Compare commits
2
Commits
88661ec960
...
ba799bd47b
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ba799bd47b | ||
|
|
45fbc088ba |
No files matched your search
@@ -0,0 +1,31 @@
|
||||
# The Problem
|
||||
|
||||
We have a `CredentialManager` implementation which can be in two states: **locked** or **unlocked**.
|
||||
|
||||
When the manager is locked, it can be unlocked with the `unlock(...)` method. Now unlocked, the `set(...)` and `get(...)` methods can be performed on the manager. Also, it can be locked again using the `lock()` method.
|
||||
|
||||
```rust
|
||||
let mut pw = CredentialManager::new();
|
||||
pw.unlock("pssst");
|
||||
pw.set("foo", "bar");
|
||||
let val = pw.get("foo");
|
||||
pw.lock();
|
||||
```
|
||||
|
||||
But with the current implementation, on a locked manager, the `get` and `set` could be performed which would result in erroneous results.
|
||||
|
||||
```rust
|
||||
let mut pw = CredentialManager::new();
|
||||
pw.set("foo", "bar"); // -> 💥
|
||||
```
|
||||
|
||||
Also, nothing prevents us from unlocking an already unlocked manager as well as locking an already locked manager.
|
||||
|
||||
```rust
|
||||
let mut pw = CredentialManager::new();
|
||||
pw.unlock("pssst");
|
||||
pw.unlock("pssst"); // -> 💥
|
||||
|
||||
let mut pw = CredentialManager::new();
|
||||
pw.lock(); // -> 💥
|
||||
```
|
||||
@@ -0,0 +1,18 @@
|
||||
# Solution 1: Runtime Checks
|
||||
|
||||
One solution to the problem would be to simply check the locked state and restrict methods to be only callable in the corrent locked state.
|
||||
|
||||
```rust
|
||||
let mut pw = CredentialManager::new();
|
||||
pw.set("foo", "bar")?; // this would bail with an error result
|
||||
pw.unlock("pssst")?;
|
||||
pw.unlock("pssst")?; // this would bail with an error result
|
||||
pw.set("foo", "bar")?;
|
||||
let val = pw.get("foo")?;
|
||||
pw.lock()?;
|
||||
pw.lock()?; // this would bail with an error result
|
||||
```
|
||||
|
||||
## Caveats
|
||||
|
||||
Performing runtime checks on the state requires returning `Result` and also requires the check and handling of erroneous results on every call to the manager.
|
||||
@@ -0,0 +1,51 @@
|
||||
# Solution 2: "Builder Pattern"
|
||||
|
||||
A better solution is to use two different structs for each state with their separate implementations. That ensured on a compiler level that the manager can not be misused in either state it is.
|
||||
|
||||
```rust
|
||||
let pw = LockedCredentialManager::new();
|
||||
pw.set("foo", "bar");
|
||||
```
|
||||
|
||||
This produces the following compiler error:
|
||||
|
||||
```
|
||||
error[E0599]: no method named `set` found for struct `LockedCredentialManager` in the current scope
|
||||
--> 03-builder-pattern\src\main.rs:45:8
|
||||
|
|
||||
5 | struct LockedCredentialManager {
|
||||
| ------------------------------ method `set` not found for this struct
|
||||
...
|
||||
45 | pw.set("foo", "bar");
|
||||
| ^^^ method not found in `LockedCredentialManager`
|
||||
```
|
||||
|
||||
And because we pass the struct by reference in the `unlock` and `lock` methods, resulting from Rust's [onwership memory model](https://doc.rust-lang.org/book/ch04-01-what-is-ownership.html), the previous object can not be used after the transformation. And because this is checked at compile time, it makes the double locking and unlocking a non-issue in production. Let's see what the following code results in.
|
||||
|
||||
```rust
|
||||
let pw = LockedCredentialManager::new();
|
||||
let mut pw = pw.unlock("pssst");
|
||||
pw.lock();
|
||||
pw.lock();
|
||||
```
|
||||
|
||||
This code results in the following compiler error:
|
||||
|
||||
```
|
||||
error[E0382]: use of moved value: `pw`
|
||||
--> 03-builder-pattern\src\main.rs:49:5
|
||||
|
|
||||
45 | let mut pw = pw.unlock("pssst");
|
||||
| ------ move occurs because `pw` has type `UnlockedCredentialManager`, which does not implement the `Copy` trait
|
||||
...
|
||||
48 | pw.lock();
|
||||
| ------ `pw` moved due to this method call
|
||||
49 | pw.lock();
|
||||
| ^^ value used here after move
|
||||
|
|
||||
note: `UnlockedCredentialManager::lock` takes ownership of the receiver `self`, which moves `pw`
|
||||
--> 03-builder-pattern\src\main.rs:36:17
|
||||
|
|
||||
36 | pub fn lock(self) -> LockedCredentialManager {
|
||||
| ^^^^
|
||||
```
|
||||
Whitespace-only changes.
@@ -1,3 +1,4 @@
|
||||
[workspace]
|
||||
resolver = "3"
|
||||
members = ["01-problem", "02-runtime-check", "03-builder-pattern", "04-typestate"]
|
||||
resolver = "3"
|
||||
Reference in new issue
Block a user