Skip to content

Commit fc3f3de

Browse files
author
tall-vase
committed
Address latest structural feedback
1 parent d55ce18 commit fc3f3de

File tree

9 files changed

+412
-327
lines changed

9 files changed

+412
-327
lines changed

src/SUMMARY.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -449,8 +449,8 @@
449449
- [Generalizing "Ownership"](idiomatic/leveraging-the-type-system/borrow-checker-invariants/generalizing-ownership.md)
450450
- [Single-use values](idiomatic/leveraging-the-type-system/borrow-checker-invariants/single-use-values.md)
451451
- [Aliasing XOR Mutability](idiomatic/leveraging-the-type-system/borrow-checker-invariants/aliasing-xor-mutability.md)
452-
- [Lifetime Relationships primer: PhantomData](idiomatic/leveraging-the-type-system/borrow-checker-invariants/phantomdata.md)
453-
- [Lifetime Relationships & External Resources](idiomatic/leveraging-the-type-system/borrow-checker-invariants/lifetime-connections.md)
452+
- [PhantomData and Types](idiomatic/leveraging-the-type-system/borrow-checker-invariants/phantomdata-01-types.md)
453+
- [PhantomData and Lifetimes](idiomatic/leveraging-the-type-system/borrow-checker-invariants/phantomdata-02-lifetimes.md)
454454

455455
---
456456

src/idiomatic/leveraging-the-type-system/borrow-checker-invariants.md

Lines changed: 47 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
---
2-
minutes: 10
2+
minutes: 15
33
---
44

55
# Using the Borrow checker to enforce Invariants
66

7-
The borrow checker, while added to enforce memory ownership, can be
8-
leveraged model other problems and prevent API misuse.
7+
The borrow checker, while added to enforce memory ownership, can be leveraged
8+
model other problems and prevent API misuse.
99

1010
```rust,editable
1111
/// Doors can be open or closed, and you need the right key to lock or unlock
@@ -53,26 +53,56 @@ fn main() {
5353

5454
<details>
5555

56-
<!-- TODO: link to typestate when that gets merged. -->
57-
5856
- The borrow checker has been used to prevent use-after-free and multiple
5957
mutable references up until this point, and we've used types to shape and
6058
restrict use of APIs already using
6159
[the Typestate pattern](../leveraging-the-type-system/typestate-pattern.md).
6260

63-
- This example uses the ownership & borrowing rules to model the locking and
64-
unlocking of a door. We can try to open a door with a key, but if it's the
65-
wrong key the door is still closed (here represented as an error) and the key
66-
persists regardless.
61+
- Language features are often introduced for a specific purpose.
62+
63+
Over time, users may develop ways of using a feature in ways that were not
64+
predicted when they were introduced.
65+
66+
In 2004, Java 5 introduced Generics with the
67+
[main stated purpose of enabling type-safe collections](https://jcp.org/en/jsr/detail?id=14).
68+
69+
Since then, users and developers of the language expanded the use of generics
70+
to other areas of type-safe API design.
71+
<!-- TODO: Reference how this was adopted -->
72+
73+
What we aim to do here is similar: Even though the borrow checker was
74+
introduced to prevent use-after-free and data races, it is just another API
75+
design tool. It can be used to model program properties that have nothing to
76+
do with preventing memory safety bugs.
77+
78+
- To use the borrow checker as a problem solving tool, we will need to "forget"
79+
that the original purpose of it is to prevent mutable aliasing in the context
80+
of preventing use-after-frees and data races.
81+
82+
We should imagine working within situations where the rules are the same but
83+
the meaning is slightly different.
84+
85+
- This example uses ownership and borrowing are used to model the state of a
86+
physical door.
87+
88+
`open_door` **consumes** a `LockedDoor` and returns a new `OpenDoor`. The old
89+
`LockedDoor` value is no longer available.
90+
91+
If the wrong key is used, the door is left locked. It is returned as an `Err`
92+
case of the `Result`.
93+
94+
It is a compile-time error to try and use a door that has already been opened.
95+
96+
- Similarly, `lock_door` consumes an `OpenDoor`, preventing closing the door
97+
twice at compile time.
98+
99+
- The rules of the borrow checker exist to prevent memory safety bugs, but the
100+
underlying logical system does not "know" what memory is.
67101

68-
- The rules of the borrow checker exist to prevent memory safety bugs. However, the underlying
69-
logical system does not "know" what memory is. All it does is enforce a
70-
specific set of rules of how different operations affect what later operations
71-
are possible.
102+
All the borrow checker does is enforce a specific set of rules of how users
103+
can order operations.
72104

73-
- Those rules can apply to many other cases: We can piggy-back onto the rules of
74-
the borrow checker to design APIs to be harder or impossible to misuse, even
75-
when there's little or no "memory safety" concerns in the problem domain. This
76-
section will walk through some of those different domains.
105+
This is just one case of piggy-backing onto the rules of the borrow checker to
106+
design APIs to be harder or impossible to misuse.
77107

78108
</details>

src/idiomatic/leveraging-the-type-system/borrow-checker-invariants/aliasing-xor-mutability.md

Lines changed: 73 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -2,98 +2,112 @@
22
minutes: 15
33
---
44

5-
# Mutually Exclusive References, or "Aliasing XOR Mutability"
5+
# Mutually Exclusive References / "Aliasing XOR Mutability"
66

77
We can use the mutual exclusion of `&T` and `&mut T` references for a single
88
value to model some constraints.
99

10-
```rust,editable,compile_fail
11-
pub struct Transaction(/* specifics omitted */);
12-
pub struct QueryResult(String);
13-
14-
pub struct DatabaseConnection {
15-
transaction: Transaction,
16-
query_results: Vec<QueryResult>,
17-
}
10+
```rust,editable
11+
pub struct QueryResult;
12+
pub struct DatabaseConnection {/* fields omitted */}
1813
1914
impl DatabaseConnection {
2015
pub fn new() -> Self {
21-
Self {
22-
transaction: Transaction(/* again, specifics omitted */),
23-
query_results: vec![],
24-
}
25-
}
26-
pub fn get_transaction(&mut self) -> &mut Transaction {
27-
&mut self.transaction
16+
Self {}
2817
}
2918
pub fn results(&self) -> &[QueryResult] {
30-
&self.query_results
31-
}
32-
pub fn commit(&mut self) {
33-
// Work omitted, including sending/clearing the transaction
34-
println!("Transaction committed!")
19+
&[] // fake results
3520
}
3621
}
3722
38-
pub fn do_something_with_transaction(transaction: &mut Transaction) {}
23+
pub struct Transaction<'a> {
24+
connection: &'a mut DatabaseConnection,
25+
}
26+
27+
impl<'a> Transaction<'a> {
28+
pub fn new(connection: &'a mut DatabaseConnection) -> Self {
29+
Self { connection }
30+
}
31+
pub fn query(&mut self, _query: &str) {
32+
// Send the query over, but don't wait for results.
33+
}
34+
pub fn commit(self) {
35+
// Finish executing the transaction and retrieve the results.
36+
}
37+
}
3938
4039
fn main() {
4140
let mut db = DatabaseConnection::new();
42-
let mut transaction = db.get_transaction();
43-
do_something_with_transaction(transaction);
44-
let assumed_the_transactions_happened_immediately = db.results(); // ❌🔨
45-
do_something_with_transaction(transaction);
46-
// Works, as the lifetime of "transaction" as a reference ended above.
47-
let assumed_the_transactions_happened_immediately_again = db.results();
48-
db.commit();
41+
42+
// The transaction `tx` mutably borrows `db`.
43+
let mut tx = Transaction::new(&mut db);
44+
tx.query("SELECT * FROM users");
45+
46+
// This won't compile because `db` is already mutably borrowed.
47+
// let results = db.results(); // ❌🔨
48+
49+
// The borrow of `db` ends when `tx` is consumed by `commit`.
50+
tx.commit();
51+
52+
// Now it is possible to borrow `db` again.
53+
let results = db.results();
4954
}
5055
```
5156

5257
<details>
5358

54-
- Aliasing XOR Mutability means "we can have multiple immutable references, a
55-
single mutable reference, but not both."
59+
- Motivation: When working with a database API, a user might imagine that
60+
transactions are being committed "as they go" and try to read results in
61+
between queries being added to the transaction. This fundamental misuse of the
62+
API could lead to confusion as to why nothing is happening.
63+
64+
While an obvious misunderstanding, situations such as this can happen in
65+
practice.
5666

57-
- This example shows how we can use the mutual exclusion of these kinds of
58-
references to dissuade a user from reading query results while using a
59-
transaction API.
67+
Ask: Has anyone misunderstood an API by not reading the docs for proper use?
68+
69+
Expect: Examples of early-career or in-university mistakes and
70+
misunderstandings.
71+
72+
As an API grows in size and user base, a smaller percentage may have "total"
73+
knowledge of the system the API represents.
74+
75+
- This example shows how we can use Aliasing XOR Mutability prevent this kind of
76+
misuse
6077

6178
This might happen if the user is working under the false assumption that the
6279
queries being written to the transaction happen "immediately" rather than
6380
being queued up and performed together.
6481

65-
- By borrowing one field of a struct via a method that returns a mutable /
66-
exclusive reference we prevent access to the other fields of that struct under
67-
a shared / non-exclusive reference until the lifetime of that borrow ends.
68-
69-
- The `transaction` field must be borrowed via a method, as the compiler can
70-
reason about borrowing different fields in mutable/shared ways simultaneously
71-
if that borrowing is done manually.
82+
- The constructor for the Transaction type takes a mutable reference to the
83+
database connection, which it holds onto that reference.
7284

73-
Demonstrate:
85+
The explicit lifetime here doesn't have to be intimidating, it just means
86+
"`Transaction` is outlived by the `DatabaseConnection` that was passed to it"
87+
in this case.
7488

75-
- Change the instances of `db.get_transaction()` and `db.results()` to manual
76-
borrows (`&mut db.transaction` and `&db.query_results` respectively) to show
77-
the difference in what the borrow checker allows.
89+
The `mut` keyword in the type lets us determine that there is just one of
90+
these references present per variable of type `DatabaseConnection`.
7891

79-
- Put the non-`main` part of this example in a module to reiterate that this
80-
manual access is not possible across module boundaries.
92+
- While a `Transaction` exists, we can't touch the `DatabaseConnection` variable
93+
that was created from it.
8194

82-
- As laid out in [generalizing ownership](generalizing-ownership.md) we can look
83-
at the ways Mutable References and Shareable References interact to see if
84-
they fit with the invariants we want to uphold for an API.
95+
Demonstrate: uncomment the `db.results()` line.
8596

86-
- In this case, having the query results not public and placed behind a getter
87-
function, we can enforce the invariant "users of this API are not looking at
88-
the query results at the same time as they are writing to a transaction."
97+
- This lifetime parameter for `Transaction` needs to come from somewhere, in
98+
this case it is derived from the lifetime of the owned `DatabaseConnection`
99+
from which an exclusive reference is being passed.
89100

90-
- The "don't look at query results while building a transaction" invariant can
91-
still be circumvented, how so?
101+
- As laid out in [generalizing ownership](generalizing-ownership.md) and
102+
[the opening slide for this section](../borrow-checker-invariants.md) we can
103+
look at the ways Mutable References and Shareable References interact to see
104+
if they fit with the invariants we want to uphold for an API.
92105

93-
- The user could access the transaction solely through `db.get_transaction()`,
94-
leaving the lifetime too temporary to prevent access to `db.results()`.
106+
- Note: The query results not being public and placed behind a getter function
107+
lets us enforce the invariant "users can only look at query results if they
108+
are not also writing to a transaction."
95109

96-
- How could we avoid this by working in other concepts from "Leveraging the
97-
Type System"?
110+
If they're publicly available to the user outside of the definition module
111+
then this invariant can be invalidated.
98112

99113
</details>
Lines changed: 22 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
---
2-
minutes: 15
2+
minutes: 5
33
---
44

5-
# Generalizing Ownership
5+
# Generalizing Ownership: Lifetimes Refresher
66

77
The logic of the borrow checker, while modelled off "memory ownership", can be
88
abstracted away from that use case to model other problems where we want to
99
prevent API misuse.
1010

11-
```rust,editable,compile_fail
11+
```rust,editable
1212
// An internal data type to have something to hold onto.
1313
pub struct Internal;
1414
// The "outer" data.
@@ -24,11 +24,11 @@ fn deny_future_use(value: Data) {}
2424
2525
fn main() {
2626
let mut value = Data(Internal);
27-
let deny_mut = shared_use(&value);
28-
let try_to_deny_immutable = exclusive_use(&mut value); // ❌🔨
29-
let more_mut_denial = &deny_mut;
27+
let shared = shared_use(&value);
28+
// let exclusive = exclusive_use(&mut value); // ❌🔨
29+
let shared_again = &shared;
3030
deny_future_use(value);
31-
let even_more_mut_denial = shared_use(&value); // ❌🔨
31+
// let shared_again_again = shared_use(&value); // ❌🔨
3232
}
3333
```
3434

@@ -38,51 +38,31 @@ fn main() {
3838
towards semantic meaning in non-memory-safety settings. Nothing is being
3939
mutated, nothing is being sent across threads.
4040

41-
- Language features are often introduced for a specific purpose.
42-
43-
Over time, users may develop ways of using that feature in ways that may have
44-
not been foreseen.
45-
46-
In 2004, Java 5 introduced Generics with the
47-
[main stated purpose of enabling type-safe collections](https://jcp.org/en/jsr/detail?id=14).
48-
49-
Since then, users and developers of the language expanded the use of generics
50-
to other areas of type-safe API design.
51-
<!-- TODO: Reference how this was adopted -->
41+
- In rust's borrow checker we have access to three different ways of "taking" a
42+
value:
5243

53-
What we aim to do here is similar: Even though the borrow checker was
54-
introduced to prevent use-after-free and data races, it is just another API
55-
design tool. It can be used to model program properties that have nothing to
56-
do with preventing memory safety bugs.
44+
- Owned value `T`. Value is dropped when the scope ends, unless it is not
45+
returned to another scope.
5746

58-
- To use the borrow checker as a problem solving tool, we will need to "forget"
59-
that the original purpose of it is to prevent mutable aliasing in the context
60-
of preventing use-after-frees and data races, instead imagining and working
61-
within situations where the rules are the same but the meaning is slightly
62-
different.
47+
- Shared Reference `&T`. Allows aliasing but prevents mutable access while
48+
shared references are in use.
6349

64-
- In rust's borrow checker we have access to three different ways of "taking" a
65-
value:
50+
- Mutable Reference `&mut T`. Only one of these is allowed to exist for a
51+
value at any one point, but can be used to create shared references.
6652

67-
<!-- TODO: actually link to the RAII section when it has been merged. -->
68-
- Owned value `T`. Very permissive case, to the point where mutability can be
69-
re-set, but demands that nothing else is using it in any context and drops
70-
the value when scope ends (unless that scope returns this value) (see:
71-
RAII.)
53+
- Ask: The two commented-out lines in `main` would cause compilation errors,
54+
Why?
7255

73-
- Mutable Reference `&mut T`. While holding onto a mutable reference we can
74-
still "dispatch" to methods and functions that take an immutable, shared
75-
reference of the value but only as long as we're not aliasing immutable,
76-
shared references to related data "after" that dispatch.
56+
1: Because the `shared` value is still aliased after the `exclusive` reference
57+
is taken.
7758

78-
- Shared Reference `&T`. Allows aliasing but prevents mutable access while any
79-
of these exist. We can't "dispatch" to methods and functions that take
80-
mutable references when all we have is a shared reference.
59+
2: Because `value` is consumed (AKA dropped) the line before the
60+
`shared_again_again` reference is taken from `&value`.
8161

8262
- Remember that every `&T` and `&mut T` has a lifetime, just one the user
8363
doesn't have to annotate or think about most of the time. We get to avoid
8464
annotating a lot of lifetimes because the rust compiler allows a user to elide
8565
the majority of them. See:
86-
[Lifetime Elision](../../../lifetimes/lifetime-elision.md).
66+
[Lifetime Elision](../../../lifetimes/lifetime-elision.md)
8767

8868
</details>

0 commit comments

Comments
 (0)