diff --git a/src/SUMMARY.md b/src/SUMMARY.md index f8941f81cd1a..742fc76b66c8 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -451,6 +451,14 @@ - [Serializer: implement Struct](idiomatic/leveraging-the-type-system/typestate-pattern/typestate-generics/struct.md) - [Serializer: implement Property](idiomatic/leveraging-the-type-system/typestate-pattern/typestate-generics/property.md) - [Serializer: Complete implementation](idiomatic/leveraging-the-type-system/typestate-pattern/typestate-generics/complete.md) + - [Borrow checking invariants](idiomatic/leveraging-the-type-system/borrow-checker-invariants.md) + - [Lifetimes and Borrows: the Abstract Rules](idiomatic/leveraging-the-type-system/borrow-checker-invariants/generalizing-ownership.md) + - [Single-use values](idiomatic/leveraging-the-type-system/borrow-checker-invariants/single-use-values.md) + - [Mutually Exclusive References / "Aliasing XOR Mutability"](idiomatic/leveraging-the-type-system/borrow-checker-invariants/aliasing-xor-mutability.md) + - [PhantomData and Types](idiomatic/leveraging-the-type-system/borrow-checker-invariants/phantomdata-01-types.md) + - [PhantomData and Types (implementation)](idiomatic/leveraging-the-type-system/borrow-checker-invariants/phantomdata-02-types-implemented.md) + - [PhantomData: Lifetimes for External Resources](idiomatic/leveraging-the-type-system/borrow-checker-invariants/phantomdata-03-lifetimes.md) + - [PhantomData: OwnedFd & BorrowedFd](idiomatic/leveraging-the-type-system/borrow-checker-invariants/phantomdata-04-borrowedfd.md) - [Token Types](idiomatic/leveraging-the-type-system/token-types.md) - [Permission Tokens](idiomatic/leveraging-the-type-system/token-types/permission-tokens.md) - [Token Types with Data: Mutex Guards](idiomatic/leveraging-the-type-system/token-types/mutex-guard.md) diff --git a/src/idiomatic/leveraging-the-type-system/borrow-checker-invariants.md b/src/idiomatic/leveraging-the-type-system/borrow-checker-invariants.md new file mode 100644 index 000000000000..6d3399cc3db5 --- /dev/null +++ b/src/idiomatic/leveraging-the-type-system/borrow-checker-invariants.md @@ -0,0 +1,116 @@ +--- +minutes: 15 +--- + +# Using the Borrow checker to enforce Invariants + +The borrow checker, while added to enforce memory ownership, can model other +problems and prevent API misuse. + +```rust,editable +/// Doors can be open or closed, and you need the right key to lock or unlock +/// one. Modelled with a Shared key and Owned door. +pub struct DoorKey { + pub key_shape: u32, +} +pub struct LockedDoor { + lock_shape: u32, +} +pub struct OpenDoor { + lock_shape: u32, +} + +fn open_door(key: &DoorKey, door: LockedDoor) -> Result { + if door.lock_shape == key.key_shape { + Ok(OpenDoor { lock_shape: door.lock_shape }) + } else { + Err(door) + } +} + +fn close_door(key: &DoorKey, door: OpenDoor) -> Result { + if door.lock_shape == key.key_shape { + Ok(LockedDoor { lock_shape: door.lock_shape }) + } else { + Err(door) + } +} + +fn main() { + let key = DoorKey { key_shape: 7 }; + let closed_door = LockedDoor { lock_shape: 7 }; + let opened_door = open_door(&key, closed_door); + if let Ok(opened_door) = opened_door { + println!("Opened the door with key shape '{}'", key.key_shape); + } else { + eprintln!( + "Door wasn't opened! Your key only opens locks with shape '{}'", + key.key_shape + ); + } +} +``` + +
+ +- We've seen the borrow checker prevent memory safety bugs (use-after-free, data + races). + +- We've also used types to shape and restrict APIs already using + [the Typestate pattern](../leveraging-the-type-system/typestate-pattern.md). + +- Language features are often introduced for a specific purpose. + + Over time, users may develop ways of using a feature in ways that were not + predicted when they were introduced. + + Java 5 introduced Generics in 2004 with the + [main stated purpose of enabling type-safe collections](https://jcp.org/en/jsr/detail?id=14). + + Adoption was slow at first, but some new projects began designing their APIs + around generics from the beginning. + + Since then, users and developers of the language expanded the use of generics + to other areas of type-safe API design: + - Class information can be held onto via Java's `Class` or Guava's + `TypeToken`. + - The Builder pattern can be implemented using Recursive Generics. + + We aim to do something similar here: Even though the borrow checker was + introduced to prevent use-after-free and data races, we treat it as just + another API design tool. + + It can be used to model program properties that have nothing to do with + preventing memory safety bugs. + +- To use the borrow checker as a problem solving tool, we will need to "forget" + that the original purpose of it is to prevent mutable aliasing in the context + of preventing use-after-frees and data races. + + We should imagine working within situations where the rules are the same but + the meaning is slightly different. + +- This example uses ownership and borrowing are used to model the state of a + physical door. + + `open_door` **consumes** a `LockedDoor` and returns a new `OpenDoor`. The old + `LockedDoor` value is no longer available. + + If the wrong key is used, the door is left locked. It is returned as an `Err` + case of the `Result`. + + It is a compile-time error to try and use a door that has already been opened. + +- Similarly, `lock_door` consumes an `OpenDoor`, preventing closing the door + twice at compile time. + +- The rules of the borrow checker exist to prevent memory safety bugs, but the + underlying logical system does not "know" what memory is. + + All the borrow checker does is enforce a specific set of rules of how users + can order operations. + + This is just one case of piggy-backing onto the rules of the borrow checker to + design APIs to be harder or impossible to misuse. + +
diff --git a/src/idiomatic/leveraging-the-type-system/borrow-checker-invariants/aliasing-xor-mutability.md b/src/idiomatic/leveraging-the-type-system/borrow-checker-invariants/aliasing-xor-mutability.md new file mode 100644 index 000000000000..ef8bd1652dce --- /dev/null +++ b/src/idiomatic/leveraging-the-type-system/borrow-checker-invariants/aliasing-xor-mutability.md @@ -0,0 +1,108 @@ +--- +minutes: 15 +--- + +# Mutually Exclusive References / "Aliasing XOR Mutability" + +We can use the mutual exclusion of `&T` and `&mut T` references to prevent data +from being used before it is ready. + +```rust,editable +pub struct QueryResult; +pub struct DatabaseConnection {/* fields omitted */} + +impl DatabaseConnection { + pub fn new() -> Self { + Self {} + } + pub fn results(&self) -> &[QueryResult] { + &[] // fake results + } +} + +pub struct Transaction<'a> { + connection: &'a mut DatabaseConnection, +} + +impl<'a> Transaction<'a> { + pub fn new(connection: &'a mut DatabaseConnection) -> Self { + Self { connection } + } + pub fn query(&mut self, _query: &str) { + // Send the query over, but don't wait for results. + } + pub fn commit(self) { + // Finish executing the transaction and retrieve the results. + } +} + +fn main() { + let mut db = DatabaseConnection::new(); + + // The transaction `tx` mutably borrows `db`. + let mut tx = Transaction::new(&mut db); + tx.query("SELECT * FROM users"); + + // This won't compile because `db` is already mutably borrowed by `tx`. + // let results = db.results(); // ❌🔨 + + // The borrow of `db` ends when `tx` is consumed by `commit()`. + tx.commit(); + + // Now it is possible to borrow `db` again. + let results = db.results(); +} +``` + +
+ +- Motivation: In this database API queries are kicked off for asynchronous + execution and the results are only available once the whole transaction is + finished. + + A user might think that queries are executed immediately, and try to read + results before they are made available. This API misuse could make the app + read incomplete or incorrect data. + + While an obvious misunderstanding, situations such as this can happen in + practice. + + Ask: Has anyone misunderstood an API by not reading the docs for proper use? + + Expect: Examples of early-career or in-university mistakes and + misunderstandings. + + As an API grows in size and user base, a smaller percentage of users has deep + knowledge of the system the API represents. + +- This example shows how we can use Aliasing XOR Mutability to prevent this kind + of misuse. + +- The code might read results before they are ready if the programmer assumes + that the queries execute immediately rather than kicked off for asynchronous + execution. + +- The constructor for the `Transaction` type takes a mutable reference to the + database connection, and stores it in the returned `Transaction` value. + + The explicit lifetime here doesn't have to be intimidating, it just means + "`Transaction` is outlived by the `DatabaseConnection` that was passed to it" + in this case. + + The reference is mutable to completely lock out the `DatabaseConnection` from + other usage, such as starting further transactions or reading the results. + +- While a `Transaction` exists, we can't touch the `DatabaseConnection` variable + that was created from it. + + Demonstrate: uncomment the `db.results()` line. Doing so will result in a + compile error, as `db` is already mutably borrowed. + +- Note: The query results not being public and placed behind a getter function + lets us enforce the invariant "users can only look at query results if there + is no active transactions." + + If the query results were placed in a public struct field, this invariant + could be violated. + +
diff --git a/src/idiomatic/leveraging-the-type-system/borrow-checker-invariants/generalizing-ownership.md b/src/idiomatic/leveraging-the-type-system/borrow-checker-invariants/generalizing-ownership.md new file mode 100644 index 000000000000..c439945e9016 --- /dev/null +++ b/src/idiomatic/leveraging-the-type-system/borrow-checker-invariants/generalizing-ownership.md @@ -0,0 +1,72 @@ +--- +minutes: 10 +--- + +# Lifetimes and Borrows: the Abstract Rules + +```rust,editable +// An internal data type to have something to hold onto. +pub struct Internal; +// The "outer" data. +pub struct Data(Internal); + +fn shared_use(value: &Data) -> &Internal { + &value.0 +} +fn exclusive_use(value: &mut Data) -> &mut Internal { + &mut value.0 +} +fn deny_future_use(value: Data) {} + +fn demo_exclusive() { + let mut value = Data(Internal); + let shared = shared_use(&value); + // let exclusive = exclusive_use(&mut value); // ❌🔨 + let shared_again = &shared; +} + +fn demo_denied() { + let value = Data(Internal); + deny_future_use(value); + // let shared = shared_use(&value); // ❌🔨 +} + +# fn main() {} +``` + +
+ +- This example re-frames the borrow checker rules away from references and + towards semantic meaning in non-memory-safety settings. + + Nothing is being mutated, nothing is being sent across threads. + +- In rust's borrow checker we have access to three different ways of "taking" a + value: + + - Owned value `T`. Value is dropped when the scope ends, unless it is not + returned to another scope. + + - Shared Reference `&T`. Allows aliasing but prevents mutable access while + shared references are in use. + + - Mutable Reference `&mut T`. Only one of these is allowed to exist for a + value at any one point, but can be used to create shared references. + +- Ask: The two commented-out lines in the `demo` functions would cause + compilation errors, Why? + + `demo_exclusive`: Because the `shared` value is still aliased after the + `exclusive` reference is taken. + + `demo_denied`: Because `value` is consumed the line before the + `shared_again_again` reference is taken from `&value`. + +- Remember that every `&T` and `&mut T` has a lifetime, just one the user + doesn't have to annotate or think about most of the time. + + We rarely specify lifetimes because the Rust compiler allows us to _elide_ + them in most cases. See: + [Lifetime Elision](../../../lifetimes/lifetime-elision.md) + +
diff --git a/src/idiomatic/leveraging-the-type-system/borrow-checker-invariants/phantomdata-01-types.md b/src/idiomatic/leveraging-the-type-system/borrow-checker-invariants/phantomdata-01-types.md new file mode 100644 index 000000000000..9f53e5af5c11 --- /dev/null +++ b/src/idiomatic/leveraging-the-type-system/borrow-checker-invariants/phantomdata-01-types.md @@ -0,0 +1,48 @@ +--- +minutes: 5 +--- + +# PhantomData 1/4: De-duplicating Same Data & Semantics + +The newtype pattern can sometimes come up against the DRY principle, how do we +solve this? + + +```rust,editable,compile_fail +pub struct UserId(u64); +impl ChatUser for UserId { /* ... */ } + +pub struct PatronId(u64); +impl ChatUser for PatronId { /* ... */ } + +pub struct ModeratorId(u64); +impl ChatUser for ModeratorId { /* ... */ } +impl ChatModerator for ModeratorId { /* ... */ } + +pub struct AdminId(u64); +impl ChatUser for AdminId { /* ... */ } +impl ChatModerator for AdminId { /* ... */ } +impl ChatAdmin for AdminId { /* ... */ } + +// And so on ... +fn main() {} +``` + + +
+ +- Problem: We want to use the newtype pattern to differentiate permissions, but + we're having to implement the same traits over and over again for the same + data. + +- Ask: Assume the details of each implementation here are the same between + types, what are ways we can avoid repeating ourselves? + + Expect: + - Make this an enum, not distinct data types. + - Bundle the user ID with permission tokens like + `struct Admin(u64, UserPermission, ModeratorPermission, AdminPermission);` + - Adding a type parameter which encodes permissions. + - Mentioning `PhantomData` ahead of schedule (it's in the title). + +
diff --git a/src/idiomatic/leveraging-the-type-system/borrow-checker-invariants/phantomdata-02-types-implemented.md b/src/idiomatic/leveraging-the-type-system/borrow-checker-invariants/phantomdata-02-types-implemented.md new file mode 100644 index 000000000000..5a3560f1aeae --- /dev/null +++ b/src/idiomatic/leveraging-the-type-system/borrow-checker-invariants/phantomdata-02-types-implemented.md @@ -0,0 +1,91 @@ +--- +minutes: 10 +--- + +# PhantomData 2/4: Type-level tagging + +Let's solve the problem from the previous slide by adding a type parameter. + + +```rust,editable +// use std::marker::PhantomData; + +pub struct ChatId { id: u64, tag: T } + +pub struct UserTag; +pub struct AdminTag; + +pub trait ChatUser {/* ... */} +pub trait ChatAdmin {/* ... */} + +impl ChatUser for UserTag {/* ... */} +impl ChatUser for AdminTag {/* ... */} // Admins are users +impl ChatAdmin for AdminTag {/* ... */} + +// impl Debug for UserTag {/* ... */} +// impl PartialEq for UserTag {/* ... */} +// impl Eq for UserTag {/* ... */} +// And so on ... + +impl ChatId {/* All functionality for users and above */} +impl ChatId {/* All functionality for only admins */} + +fn main() {} +``` + + +
+ +- Here we're using a type parameter and gating permissions behind "tag" types + that implement different permission traits. + + Tag types, or marker types, are zero-sized types that have some semantic + meaning to users and API designers. + +- Ask: What issues does having it be an actual instance of that type pose? + + Answer: If it's not a zero-sized type (like `()` or `struct MyTag;`), then + we're allocating more memory than we need to when all we care for is type + information that is only relevant at compile-time. + +- Demonstrate: remove the `tag` value entirely, then compile! + + This won't compile, as there's an unused (phantom) type parameter. + + This is where `PhantomData` comes in! + +- Demonstrate: Uncomment the `PhantomData` import, and make `ChatId` the + following: + + ```rust,compile_fail + pub struct ChatId { + id: u64, + tag: PhantomData, + } + ``` + +- `PhantomData` is a zero-sized type with a type parameter. We can construct + values of it like other ZSTs with + `let phantom: PhantomData = PhantomData;` or with the + `PhantomData::default()` implementation. + + Demonstrate: implement `From` for `ChatId`, emphasizing the + construction of `PhantomData` + + ```rust,compile_fail + impl From for ChatId { + fn from(value: u64) -> Self { + ChatId { + id: value, + // Or `PhantomData::default()` + tag: PhantomData, + } + } + } + ``` + +- `PhantomData` can be used as part of the Typestate pattern to have data with + the same structure but different methods, e.g., have `TaggedData` + implement methods or trait implementations that `TaggedData` doesn't. + +
diff --git a/src/idiomatic/leveraging-the-type-system/borrow-checker-invariants/phantomdata-03-lifetimes.md b/src/idiomatic/leveraging-the-type-system/borrow-checker-invariants/phantomdata-03-lifetimes.md new file mode 100644 index 000000000000..1abfae16bc24 --- /dev/null +++ b/src/idiomatic/leveraging-the-type-system/borrow-checker-invariants/phantomdata-03-lifetimes.md @@ -0,0 +1,114 @@ +--- +minutes: 15 +--- + +# PhantomData 3/4: Lifetimes for External Resources + +The invariants of external resources often match what we can do with lifetime +rules. + +```rust,editable +// use std::marker::PhantomData; + +/// Direct FFI to a database library in C. +/// We got this API as is, we have no influence over it. +mod ffi { + pub type DatabaseHandle = u8; // maximum 255 databases open at the same time + + fn database_open(name: *const std::os::raw::c_char) -> DatabaseHandle { + unimplemented!() + } + // ... etc. +} + +struct DatabaseConnection(ffi::DatabaseHandle); +struct Transaction<'a>(&'a mut DatabaseConnection); + +impl DatabaseConnection { + fn new_transaction(&mut self) -> Transaction<'_> { + Transaction(self) + } +} + +fn main() {} +``` + +
+ +- Remember the transaction API from the + [Aliasing XOR Mutability](./aliasing-xor-mutability.md) example. + + We held onto a mutable reference to the database connection within the + transaction type to lock out the database while a transaction is active. + + In this example, we want to implement a `Transaction` API on top of an + external, non-Rust API. + + We start by defining a `Transaction` type that holds onto + `&mut DatabaseConnection`. + +- Ask: What are the limits of this implementation? Assume the `u8` is accurate + implementation-wise and enough information for us to use the external API. + + Expect: + - Indirection takes up 7 bytes more than we need to on a 64-bit platform, as + well as costing a pointer dereference at runtime. + +- Problem: We want the transaction to borrow the database connection that + created it, but we don't want the `Transaction` object to store a real + reference. + +- Ask: What happens when we remove the mutable reference in `Transaction` while + keeping the lifetime parameter? + + Expect: Unused lifetime parameter! + +- Like with the type tagging from the previous slides, we can bring in + `PhantomData` to capture this unused lifetime parameter for us. + + The difference is that we will need to use the lifetime alongside another + type, but that other type does not matter too much. + +- Demonstrate: change `Transaction` to the following: + + ```rust,compile_fail + pub struct Transaction<'a> { + connection: DatabaseConnection, + _phantom: PhantomData<&mut 'a ()>, + } + ``` + + Update the `DatabaseConnection::new_transaction()` method: + + ```rust,compile_fail + fn new_transaction<'a>(&'a mut self) -> Transaction<'a> { + Transaction { connection: DatabaseConnection(self.0), _phantom: PhantomData } + } + ``` + + This gives an owned database connection that is tied to the + `DatabaseConnection` that created it, but with less runtime memory footprint + that the store-a-reference version did. + + Because `PhantomData` is a zero-sized type (like `()` or + `struct MyZeroSizedType;`), the size of `Transaction` is now the same as `u8`. + + The implementation that held onto a reference instead was as large as a + `usize`. + +## More to Explore + +- This way of encoding relationships between types and values is very powerful + when combined with unsafe, as the ways one can manipulate lifetimes becomes + almost arbitrary. This is also dangerous, but when combined with tools like + external, mechanically-verified proofs we can safely encode + cyclic/self-referential types while encoding lifetime & safety expectations in + the relevant data types. + +- The [GhostCell (2021)](https://plv.mpi-sws.org/rustbelt/ghostcell/) paper and + its [relevant implementation](https://gitlab.mpi-sws.org/FP/ghostcell) show + this kind of work off. While the borrow checker is restrictive, there are + still ways to use escape hatches and then _show that the ways you used those + escape hatches are consistent and safe._ + +
diff --git a/src/idiomatic/leveraging-the-type-system/borrow-checker-invariants/phantomdata-04-borrowedfd.md b/src/idiomatic/leveraging-the-type-system/borrow-checker-invariants/phantomdata-04-borrowedfd.md new file mode 100644 index 000000000000..663a3faa6c09 --- /dev/null +++ b/src/idiomatic/leveraging-the-type-system/borrow-checker-invariants/phantomdata-04-borrowedfd.md @@ -0,0 +1,112 @@ +--- +minutes: 10 +--- + +# PhantomData 4/4: OwnedFd & BorrowedFd + +`BorrowedFd` is a prime example of `PhantomData` in action. + + + +```rust,editable +use std::marker::PhantomData; +use std::os::raw::c_int; + +mod libc_ffi { + use std::os::raw::{c_char, c_int}; + pub unsafe fn open(path: *const c_char, oflag: c_int) -> c_int { + 3 + } + pub unsafe fn close(fd: c_int) {} +} + +struct OwnedFd { + fd: c_int, +} + +impl OwnedFd { + fn try_from_fd(fd: c_int) -> Option { + if fd < 0 { + return None; + } + Some(OwnedFd { fd }) + } + + fn as_fd<'a>(&'a self) -> BorrowedFd<'a> { + BorrowedFd { fd: self.fd, _phantom: PhantomData } + } +} + +impl Drop for OwnedFd { + fn drop(&mut self) { + unsafe { libc_ffi::close(self.fd) }; + } +} + +struct BorrowedFd<'a> { + fd: c_int, + _phantom: PhantomData<&'a ()>, +} + +fn main() { + // Create a file with a raw syscall with write-only and create permissions. + let fd = unsafe { libc_ffi::open(c"c_str.txt".as_ptr(), 065) }; + // Pass the ownership of an integer file descriptor to an `OwnedFd`. + // `OwnedFd::drop()` closes the file descriptor. + let owned_fd = + OwnedFd::try_from_fd(fd).expect("Could not open file with syscall!"); + + // Create a `BorrowedFd` from an `OwnedFd`. + // `BorrowedFd::drop()` does not close the file because it doesn't own it! + let borrowed_fd: BorrowedFd<'_> = owned_fd.as_fd(); + // std::mem::drop(owned_fd); // ❌🔨 + std::mem::drop(borrowed_fd); + let second_borrowed = owned_fd.as_fd(); + // owned_fd will be dropped here, and the file will be closed. +} +``` + +
+ +- A file descriptor represents a specific process's access to a file. + + Reminder: Device and OS-specific features are exposed as if they were files on + unix-style systems. + +- [`OwnedFd`](https://rust-lang.github.io/rfcs/3128-io-safety.html#ownedfd-and-borrowedfdfd) + is an owned wrapper type for a file descriptor. It _owns_ the file descriptor, + and closes it when dropped. + + Note: We have our own implementation of it here, draw attention to the + explicit `Drop` implementation. + + `BorrowedFd` is its borrowed counterpart, it does not need to close the file + when it is dropped. + + Note: We have not explicitly implemented `Drop` for `BorrowedFd`. + +- `BorrowedFd` uses a lifetime captured with a `PhantomData` to enforce the + invariant "if this file descriptor exists, the OS file descriptor is still + open even though it is not responsible for closing that file descriptor." + + The lifetime parameter of `BorrowedFd` demands that there exists another value + in your program that lasts as long as that specific `BorrowedFd` or outlives + it (in this case an `OwnedFd`). + + Demonstrate: Uncomment the `std::mem::drop(owned_fd)` line and try to compile + to show that `borrowed_fd` relies on the lifetime of `owned_fd`. + + This has been encoded by the API designers to mean _that other value is what + keeps the access to the file open_. + + Because Rust's borrow checker enforces this relationship where one value must + last at least as long as another, users of this API do not need to worry about + handling this correct file descriptor aliasing and closing logic themselves. + +
diff --git a/src/idiomatic/leveraging-the-type-system/borrow-checker-invariants/single-use-values.md b/src/idiomatic/leveraging-the-type-system/borrow-checker-invariants/single-use-values.md new file mode 100644 index 000000000000..a3e7e47052b3 --- /dev/null +++ b/src/idiomatic/leveraging-the-type-system/borrow-checker-invariants/single-use-values.md @@ -0,0 +1,80 @@ +--- +minutes: 10 +--- + +# Single-use values + +Sometimes we want values that _can only be used once_. One critical example of +this is in cryptography: A "Nonce." + +```rust,editable +pub struct Key(/* specifics omitted */); +/// A single-use number suitable for cryptographic purposes. +pub struct Nonce(u32); +/// A cryptographically sound random generator function. +pub fn new_nonce() -> Nonce { + Nonce(4) // chosen by a fair dice roll, https://xkcd.com/221/ +} +/// Consume a nonce, but not the key or the data. +pub fn encrypt(nonce: Nonce, key: &Key, data: &[u8]) {} + +fn main() { + let nonce = new_nonce(); + let data_1: [u8; 4] = [1, 2, 3, 4]; + let data_2: [u8; 4] = [4, 3, 2, 1]; + let key = Key(/* specifics omitted */); + + // The key and data can be re-used, copied, etc. but the nonce cannot. + encrypt(nonce, &key, &data_1); + // encrypt(nonce, &key, &data_2); // 🛠️❌ +} +``` + +
+ +- Problem: How can we guarantee a value is used only once? + +- Motivation: A nonce is a piece of random, unique data used in cryptographic + protocols to prevent replay attacks. + + Background: In practice people have ended up accidentally re-using nonces. + Most commonly, this causes the cryptographic protocol to completely break down + and stop fulfilling its function. + + Depending on the specifics of nonce reuse and cryptography at hand, private + keys can also become computable by attackers. + +- Rust has an obvious tool for achieving the invariant "Once you use this, you + can't use it again": passing a value as an _owned argument_. + +- Highlight: the `encrypt` function takes `nonce` by value (an owned argument), + but `key` and `data` by reference. + +- The technique for single-use values is as follows: + + - Keep constructors private, so a user can't construct values with the same + inner value twice. + + - Don't implement `Clone`/`Copy` traits or equivalent methods, so a user can't + duplicate data we want to keep unique. + + - Make the interior type opaque (like with the newtype pattern), so the user + cannot modify an existing value on their own. + +- Ask: What are we missing from the newtype pattern in the slide's code? + + Expect: Module boundary. + + Demonstrate: Without a module boundary a user can construct a nonce on their + own. + + Fix: Put `Key`, `Nonce`, and `new_nonce` behind a module. + +## More to Explore + +- Cryptography Nuance: A nonce might still be used twice if it was created + through pseudo-random process with no actual randomness. That can't be + prevented through this method. This API design prevents one nonce duplication, + but not all logic bugs. + +