From e262c0f195ad874c0d543074610834c54797888b Mon Sep 17 00:00:00 2001 From: LukeMathWalker <20745048+LukeMathWalker@users.noreply.github.com> Date: Wed, 2 Jul 2025 15:54:19 +0200 Subject: [PATCH] Semantic confusion --- src/SUMMARY.md | 1 + .../newtype-pattern/semantic-confusion.md | 72 +++++++++++++++++++ 2 files changed, 73 insertions(+) create mode 100644 src/idiomatic/leveraging-the-type-system/newtype-pattern/semantic-confusion.md diff --git a/src/SUMMARY.md b/src/SUMMARY.md index dedf9028..1bef7787 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -434,6 +434,7 @@ - [Welcome](idiomatic/welcome.md) - [Leveraging the Type System](idiomatic/leveraging-the-type-system.md) - [Newtype Pattern](idiomatic/leveraging-the-type-system/newtype-pattern.md) + - [Semantic Confusion](idiomatic/leveraging-the-type-system/newtype-pattern/semantic-confusion.md) --- diff --git a/src/idiomatic/leveraging-the-type-system/newtype-pattern/semantic-confusion.md b/src/idiomatic/leveraging-the-type-system/newtype-pattern/semantic-confusion.md new file mode 100644 index 00000000..648257b6 --- /dev/null +++ b/src/idiomatic/leveraging-the-type-system/newtype-pattern/semantic-confusion.md @@ -0,0 +1,72 @@ +--- +minutes: 5 +--- + +# Semantic Confusion + +There is room for confusion whenever a function takes multiple arguments of the +same type: + +```rust +# struct LoginError; +pub fn login(username: &str, password: &str) -> Result<(), LoginError> { + // [...] + # Ok(()) +} + +# let password = "password"; +# let username = "username"; +// In another part of the codebase, we swap arguments by mistake. +// Bug (best case), security vulnerability (worst case) +login(password, username); +``` + +The newtype pattern can be used to prevent this class of errors at compile time: + +```rust +pub struct Username(String); +pub struct Password(String); +# struct LoginError; + +pub fn login(username: &Username, password: &Password) -> Result<(), LoginError> { + // [...] + # Ok(()) +} + +# let password = Password("password".into()); +# let username = Username("username".into()); +// Compiler error 🎉 +login(password, username); +``` + +
+ +- Run both examples to show students the successful compilation for the original + example, and the compiler error returned by the modified example. + +- Stress the _semantic_ angle. The newtype pattern should be leveraged to use + distinct types for distinct concepts, thus ruling out this class of errors + entirely. + +- Nonetheless, note that there are legitimate scenarios where a function may + take multiple arguments of the same type. In those scenarios, if correctness + is of paramount important, consider using a struct with named fields as input: + ```rust + pub struct LoginArguments { + pub username: &str, + pub password: &str, + } + # fn login(i: LoginArguments) {} + # let password = "password"; + # let username = "username"; + + // No need to check the definition of the `login` function to spot the issue. + login(LoginArguments { + username: password, + password: username, + }) + ``` + Users are forced, at the callsite, to assign values to each field, thus + increasing the likelihood of spotting bugs. + +