Skip to content

Commit e262c0f

Browse files
Semantic confusion
1 parent 1508dbb commit e262c0f

File tree

2 files changed

+73
-0
lines changed

2 files changed

+73
-0
lines changed

src/SUMMARY.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -434,6 +434,7 @@
434434
- [Welcome](idiomatic/welcome.md)
435435
- [Leveraging the Type System](idiomatic/leveraging-the-type-system.md)
436436
- [Newtype Pattern](idiomatic/leveraging-the-type-system/newtype-pattern.md)
437+
- [Semantic Confusion](idiomatic/leveraging-the-type-system/newtype-pattern/semantic-confusion.md)
437438

438439
---
439440

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
---
2+
minutes: 5
3+
---
4+
5+
# Semantic Confusion
6+
7+
There is room for confusion whenever a function takes multiple arguments of the
8+
same type:
9+
10+
```rust
11+
# struct LoginError;
12+
pub fn login(username: &str, password: &str) -> Result<(), LoginError> {
13+
// [...]
14+
# Ok(())
15+
}
16+
17+
# let password = "password";
18+
# let username = "username";
19+
// In another part of the codebase, we swap arguments by mistake.
20+
// Bug (best case), security vulnerability (worst case)
21+
login(password, username);
22+
```
23+
24+
The newtype pattern can be used to prevent this class of errors at compile time:
25+
26+
```rust
27+
pub struct Username(String);
28+
pub struct Password(String);
29+
# struct LoginError;
30+
31+
pub fn login(username: &Username, password: &Password) -> Result<(), LoginError> {
32+
// [...]
33+
# Ok(())
34+
}
35+
36+
# let password = Password("password".into());
37+
# let username = Username("username".into());
38+
// Compiler error 🎉
39+
login(password, username);
40+
```
41+
42+
<details>
43+
44+
- Run both examples to show students the successful compilation for the original
45+
example, and the compiler error returned by the modified example.
46+
47+
- Stress the _semantic_ angle. The newtype pattern should be leveraged to use
48+
distinct types for distinct concepts, thus ruling out this class of errors
49+
entirely.
50+
51+
- Nonetheless, note that there are legitimate scenarios where a function may
52+
take multiple arguments of the same type. In those scenarios, if correctness
53+
is of paramount important, consider using a struct with named fields as input:
54+
```rust
55+
pub struct LoginArguments {
56+
pub username: &str,
57+
pub password: &str,
58+
}
59+
# fn login(i: LoginArguments) {}
60+
# let password = "password";
61+
# let username = "username";
62+
63+
// No need to check the definition of the `login` function to spot the issue.
64+
login(LoginArguments {
65+
username: password,
66+
password: username,
67+
})
68+
```
69+
Users are forced, at the callsite, to assign values to each field, thus
70+
increasing the likelihood of spotting bugs.
71+
72+
</details>

0 commit comments

Comments
 (0)