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
@gribozavr gribozavr requested review from gribozavr and removed request for LukeMathWalker September 21, 2025 22:56
Copy link
Collaborator

@djmitche djmitche left a comment

Choose a reason for hiding this comment

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

I apologize for the enormous delay here -- it's been a busy couple of months!

This looks great, and I appreciate seeing previous comments included here.

@gribozavr gribozavr self-assigned this Oct 3, 2025
Copy link
Collaborator

@gribozavr gribozavr left a comment

Choose a reason for hiding this comment

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

First pass, generally looking good, but I left some comments that may lead to structural changes, so I expect to do another detailed pass after you land some changes.

@@ -0,0 +1,80 @@
---
minutes: 0
Copy link
Collaborator

Choose a reason for hiding this comment

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

Please add the timing information.


- By keeping constructors private and not implementing clone/copy for a type,
making the interior type opaque (as per the newtype pattern), we can prevent
multiple uses of the same, API-controlled value.
Copy link
Collaborator

Choose a reason for hiding this comment

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

Not clear what "API-controlled" means here.

do_something_with_transaction(transaction);
let assumed_the_transactions_happened_immediately = db.results(); // ❌🔨
do_something_with_transaction(transaction);
// Works, as the lifetime of "transaction" as a reference ended above.
Copy link
Collaborator

Choose a reason for hiding this comment

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

I might be misunderstanding your intent behind this example, but it does not feel realistic realistic because the database didn't get a notification that the borrow has ended, and thus could not have started executing it. In other words, it is not clear what the DatabaseConnection type achieved by being locked out while the transaction was borrowed out.

Maybe you can fix it by wrapping the reference in a custom wrapper type which implements Drop? Ideally, that drop() would also print a message (like "executing query...") to make it clear to the audience that the database can indeed fill in the result list.


# Lifetime "Connections" & External Resources

Using `PhantomData` in conjunction with lifetimes lets us say "this value may
Copy link
Collaborator

Choose a reason for hiding this comment

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

This made me think, do we have a slide on PhantomData, just about the concept by itself? Should we have one?

It seems like there are a few things to say to motivate PhantomData's existence (that is, before we even introduce it on the slide), in particular that the compiler infers certain properties of a user-defined type based on its fields - this would be surprising to many people, I think.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

We do not. Prior to starting work on this chapter there wasn't a mention of PhantomData in the repository at all.

If there's a place for it to exist, it's likely in a revision on the typestate work. I'll pull together something short that can slot in just before the lifetime-connections slide for now under the assumption it can be moved about as needed.

majority of them. See:
[Lifetime Elision](../../../lifetimes/lifetime-elision.md).

</details>
Copy link
Collaborator

Choose a reason for hiding this comment

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

This slide might be a place where an analogy might help some listeners understand what we are trying to get at when we say that the borrow checker and lifetimes are just another API design tool. Here's one possible analogy. WDYT?

Generics in Java were added primarily to support type-safe collections. In fact, Java 5 added generic type arguments to existing standard library collection types that were previously non-generic! So the language designers had a clear primary use case in mind. However, generics turned out to be useful in many other API designs. So it would be too narrow-minded to present Java generics as "a language feature for type-safe collections."

Similarly, the lifetimes and the borrow checker were introduced in Rust for compile-time memory safety guarantees, but their applicability in API design is broader. We (the Rust community) are still discovering design patterns and trying to understand what these tools can do for API design beyond memory safety.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Seems like a good thing to bring up, I'll pull together some references to drop in. If you've got suggestions on pieces covering this I'd be happy to hear about them, but I understand linkrot and the ephemeral nature of back-channel discussion of the time may have gotten to most of it.

Copy link
Collaborator

@gribozavr gribozavr Oct 10, 2025

Choose a reason for hiding this comment

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

JSR 14 which introduced generics lists only one goal specific to a use case, and it is "Good collections support. The core Collections APIs and similar APIs are perhaps the most important customers of genericity, so it is essential that they work well as, and with, generic classes." Furthermore, this is the #1 goal of the proposal overall.

An empirical research article that I could find, Java generics adoption: how new features are introduced, championed, or ignored studies how generics were adopted in practice. It includes a data-driven argument that the most common parameterized types are collections (the only non-collection-related type in Table 1 is Class<?>). This aligns with my intuition: the primary use case is collections, but there are other cases where generics turned out to be useful (for example, Class<?> in the standard library, TypeToken<?> in Google's Guava to work around type erasure in Java's generics, or the "recursive generics" pattern similar to CRTP in C++).

One source that I had high hopes for, ACM's History of programming languages journal, unfortunately does have a piece on Java.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Wonderful, thank you!

@gribozavr gribozavr assigned tall-vase and unassigned gribozavr Oct 8, 2025
@gribozavr gribozavr added the waiting for author Waiting on the issue author to reply label Oct 8, 2025
&self.query_results
}
pub fn commit(&mut self) {
/* Work omitted, including sending/clearing the transaction */
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
/* Work omitted, including sending/clearing the transaction */
// Work omitted, including sending/clearing the transaction

```rust,editable
mod cryptography {
pub struct Key(/* specifics omitted */);
// Pretend this is a cryptographically sound, single-use number.
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
// Pretend this is a cryptographically sound, single-use number.
// A single-use number suitable for cryptographic purposes.

pub struct Key(/* specifics omitted */);
// Pretend this is a cryptographically sound, single-use number.
pub struct Nonce(u32);
// And pretend this is cryptographically sound random generator function.
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
// And pretend this is cryptographically sound random generator function.
// A cryptographically sound random generator function.

pub struct Nonce(u32);
// And pretend this is cryptographically sound random generator function.
pub fn new_nonce() -> Nonce {
Nonce(std::time::UNIX_EPOCH.elapsed().unwrap_or_default().subsec_nanos())
Copy link
Collaborator

@gribozavr gribozavr Oct 10, 2025

Choose a reason for hiding this comment

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

Suggested change
Nonce(std::time::UNIX_EPOCH.elapsed().unwrap_or_default().subsec_nanos())
Nonce(4) // chosen by a fair dice roll, https://xkcd.com/221/

The timestamp was not a good RNG anyway, and showing a good one is not the point of the example. With a bit of lighthearted humor we can telegraph that this is a pretend-RNG.

Nonce(std::time::UNIX_EPOCH.elapsed().unwrap_or_default().subsec_nanos())
}

// We consume a nonce, but not the key or the data.
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
// We consume a nonce, but not the key or the data.
// Consume a nonce, but not the key or the data.

Comment on lines +22 to +24
}

use cryptography::*;
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
}
use cryptography::*;

Comment on lines +41 to +42
- When a new feature is introduced to users, it is often done so with a specific
idea of what it will be used for.
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
- When a new feature is introduced to users, it is often done so with a specific
idea of what it will be used for.
- Language features are often introduced for a specific purpose.

not been foreseen.

In 2004, Java 5 introduced Generics with the
[main stated purpose of enabling type safe collections](https://jcp.org/en/jsr/detail?id=14).
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
[main stated purpose of enabling type safe collections](https://jcp.org/en/jsr/detail?id=14).
[main stated purpose of enabling type-safe collections](https://jcp.org/en/jsr/detail?id=14).

[main stated purpose of enabling type safe collections](https://jcp.org/en/jsr/detail?id=14).

Since then, users and developers of the language expanded the use of generics
to other areas of type safe API design.
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
to other areas of type safe API design.
to other areas of type-safe API design.

Comment on lines +54 to +57
What we aim to do here is similar: The borrow checker, after being introduced
to people with the purpose of avoiding use-after-free and data races, is being
used to model things that have nothing to do with preventing those classes of
misuse.
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
What we aim to do here is similar: The borrow checker, after being introduced
to people with the purpose of avoiding use-after-free and data races, is being
used to model things that have nothing to do with preventing those classes of
misuse.
What we aim to do here is similar: Even though the borrow checker was introduced
to prevent use-after-free and data races, it is just another API design tool. It can be
used to model program properties that have nothing to do with preventing memory safety bugs.

exclusive reference we prevent access to the other fields of that struct under
a shared / non-exclusive reference until the lifetime of that borrow ends.

Note: This has to be via a method, as the compiler can reason about borrowing
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
Note: This has to be via a method, as the compiler can reason about borrowing
- The `transaction` field must be borrowed via a method, as the compiler can reason about borrowing

This is a major part (esp. since now there's a demonstration), so it deserves a top-level bullet point.

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

Labels

deep_dives/idiomatic waiting for author Waiting on the issue author to reply

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants