Skip to content

Conversation

tall-vase
Copy link
Collaborator

Adds materials on the "leveraging the type system/borrow checker invariants" subject.

I'm still calibrating what's expected subject-and-style wise, so do spell out things where I've drifted off mark.

Copy link

google-cla bot commented Sep 1, 2025

Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

View this failed invocation of the CLA check for more information.

For the most up to date status, view the checks section at the bottom of the pull request.

@tall-vase tall-vase changed the title "borrow checker invariants" section of the "borrow checker invariants" section of the "leveraging the type system" chapter Sep 1, 2025

- The borrow checker has been used to prevent use-after-free and multiple mutable references up until this point.

- This example uses the ownership & borrowing rules to model the opening and closing of a door. We can try to open a door with a key, but if it's the wrong key the door is still closed (here represented as an error.)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is indeed the case in the example, but it's not related to ownership/borrowing, isn't it?
If I'm interpreting things correctly, the example connects to the topic by intentionally consuming a door by value whenever it changes state, thus making it impossible for a door to be both open and closed at the same time.
We should point the instructor in that direction, if that's what we mean by it.

@@ -0,0 +1,46 @@
---
minutes: 0
---
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing the title on this one.

Comment on lines +8 to +26
pub struct InternalData;
pub struct Value(InternalData);

fn shared_use(value: &Value) -> &InternalData {
&value.0
}

fn exclusive_use(value: &mut Value) -> &mut InternalData {
&mut value.0
}

fn deny_future_use(value: Value) {}

let mut value = Value(InternalData);
let deny_mut = shared_use(&value);
let try_to_deny_immutable = exclusive_use(&mut value); // ❌🔨
let more_mut_denial = &deny_mut;
deny_future_use(value);
let even_more_mut_denial = shared_use(&value); // ❌🔨
Copy link
Contributor

@LukeMathWalker LukeMathWalker Sep 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the speaker notes, we should elaborate as to what the example is trying to show and how it relates to the concepts we're discussing.
This is a bit too implicit for me on this slide.

Comment on lines +9 to +34
```rust,editable
fn main() {
pub struct Key;

// Pretend this is a cryptographically unique, use-once number.
pub struct Nonce(u32);

// It's unsafe to declare a nonce directly! In practice,
// this would be done with an RNG source, and potentially
// a timestamp.
unsafe fn new_nonce_from_raw(nonce: u32) -> Nonce {
Nonce(nonce)
}

let nonce = unsafe { new_nonce_from_raw(1337) };
let data_1: [u8; 4] = [1, 2, 3, 4];
let data_2: [u8; 4] = [4, 3, 2, 1];
let key = Key;

// The key and data can be re-used, copied, etc. but the nonce cannot.
fn encrypt(nonce: Nonce, key: &Key, data: &[u8]) {}

encrypt(nonce, &key, &data_1);
encrypt(nonce, &key, &data_2); // 🛠️❌
}
```
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the new_non_from_raw/unsafe aspect add unnecessary noise to the example. We don't need to go into the details as to how a Nonce is constructed, we just need a constructor/generator to exist.


<details>

- This example shows how we can use the "Aliasing XOR Mutability" rule when it comes to shared and mutable references to model safe access to transactions for a database. This is a loose sketch of such a model, and rust database frameworks you use may not look anything like this in practice.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might be useful to recall in layman's terms what "Aliasing XOR Mutability" implies, since a lot of folks may not be familiar with that precise terminology.


- As laid out in [generalizing ownership]("./generalizing-ownership.md") we can look at the ways Mutable References and Shareable References interact to see if they fit with the invariants we want to uphold for an API.

- Here we want to be able to write to a transaction, which has some internal breaking of rust rules we don't concern ourselves with, before then committing that transaction.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

which has some internal breaking of rust rules we don't concern ourselves with

I don't follow what this means here 🤔

Comment on lines +45 to +46
<!-- Entirely reasonable to reframe the example off this contradiction, but I think it has pedagogical value regardless. -->
- Tangential: We could instead have the `get_transaction` method return a mutable reference off a mutable reference to self (`fn get_transaction(&mut self) -> &mut TransactionInterface`) but we're trying to show off the ways shareable and mutable references exclude each other here.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-reading the example, it does feel somewhat too artificial for showing off this specific concept (e.g. compared to using a Transaction and commit as a way to showcase that you can "consume" to end the lifecycle of a resource).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants