-
Notifications
You must be signed in to change notification settings - Fork 1.9k
"Token Types" chapter of Idiomatic Rust #2921
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
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. |
The previous STYLE.md advised against using speaker notes as a 'script' but didn't provide clear examples of what that meant. This could be interpreted by LLMs as disallowing any kind of instructor prompt. This change clarifies the guideline by: 1. Distinguishing between long-form, verbatim 'scripts' (bad) and short, actionable 'teaching prompts' (good). 2. Providing concrete 'good' and 'bad' examples to make the distinction unambiguous for future contributors.
…all-vase/comprehensive-rust into idiomatic/typesystem-tokens
> | ||
> - Note: The `Ext` suffix is a common convention. | ||
> - Ask: What happens if the `use` statement is removed? | ||
> - Demo: Comment out the `use` statement to show the compiler error. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this content already exists in the main branch? Please rebase to hide the spurious diff.
|
||
<details> | ||
|
||
- Token types let us use the privacy tools of types and modules to control when |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This explanation is a bit too abstract and sounds very similar to how we introduce the typestate pattern, "How can we ensure that only valid operations are allowed on a value based on its current state?", except that we're not talking about a specific state. Could we make it more concrete? Should we merge the two sections? Should this section be ordered before typestate but after newtype?
My thinking is that newtype wraps a concrete piece of data, and represents a proof that the data satisfies a predicate.
What I see in the token types section suggests that a token type represents a proof that some data elsewhere, or the program control flow, satisfies a certain predicate.
The current progression seems to be newtype -> typestate -> token types.
This does not seem ideal. First we introduce a foundational concept (Newtype), then jump to a complex application (the generic serializer in Typestate), and then present a simpler concept (token types). It seems to me that token types are a more straightforward extension of the initial newtype pattern.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can't quite do that if we're covering branded types, as that depends on the Borrowck invariants work (introduction of PhantomData, use of Lifetimes to enforce invariants). If we cut the Branded types content, then this re-ordering would be feasible.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, I tried referencing those sections, but I don't understand. The token types slide does not depend on PhantomData or complex use of lifetimes. Could you explain this issue in more detail?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To clarify, I don't mean moving the entirety of this PR between newtype and typestate. Only this slide.
src/idiomatic/leveraging-the-type-system/token-types/mutex-guard.md
Outdated
Show resolved
Hide resolved
src/idiomatic/leveraging-the-type-system/token-types/mutex-guard.md
Outdated
Show resolved
Hide resolved
src/idiomatic/leveraging-the-type-system/token-types/branded-tokens.md
Outdated
Show resolved
Hide resolved
make sure cells can't "escape" a context where we know where cyclic operations | ||
are safe. | ||
|
||
</details> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is all technically correct, but too dense for a slide. I think we need to have a more gentle flow, broken down into multiple slides, starting with motivation (in this example it is not clear what the token is achieving). When we move on to explain the implementation, we should first start with a sketch that has the right structure but omits some of the details that make the code correct for the sake of brevity (e.g., the variance mechanics).
Here's an idea, but you can of course come up with your own.
Motivation: if you decide to use the AdminToken
example in the previous slide, you can build a segue to this idea. Remind the students that the web server processes multiple requests, but only one at a time. We could use one user's credentials to log in and get an AdminToken
, stash it in a global variable somewhere, and then process other user's message deletion request using that token.
=> Idea for a solution: the Board::delete_message()
function should only accept a token that was returned by Board::login()
by the same object.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You made great improvements, but the example is still abstract.
Co-authored-by: Dmitri Gribenko <[email protected]>
Co-authored-by: Dmitri Gribenko <[email protected]>
|
||
# Permission Tokens | ||
|
||
Token types work well as a trivial "proof of checked permission." |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Token types work well as a trivial "proof of checked permission." | |
Token types work well as a proof of checked permission. |
- Ask: What is the purpose of the `proof: ()` field here? | ||
|
||
Without `proof: ()`, `Token` would have no private fields and users would be | ||
able to construct values of `Token` arbitrarily. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be nice to prompt the instructor to try to construct the token in main and show that the compiler does not allow doing that.
until they've performed a specific task. | ||
|
||
We can do this by defining a type the API consumer cannot construct on their | ||
own, through privacy tools of structs and modules. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be nice remind students that the newtype pattern is based on the same idea.
# Token Types with Data: Mutex Guards | ||
|
||
Sometimes, a token type needs additional data. A mutex guard is an example of | ||
permission + data. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
permission + data. | |
a permission + data. |
`Mutex` while the underlying `Mutex` keeps that data private from the user. | ||
|
||
- If `mutex.lock()` does not return a `MutexGuard`, you don't have permission to | ||
change the value within the mutex. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's important to emphasize here is that not only the user does not have permission (which is also true, e.g., for C++ mutexes), the user does not have a way to access the data (which sets Rust mutexes apart).
// We want a function whose lifetime is specific to each time | ||
// `new` is called, not tied to any one data structure other than |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// We want a function whose lifetime is specific to each time | |
// `new` is called, not tied to any one data structure other than | |
// We want a function whose lifetime is specific to each call to | |
// `new`, not tied to any one data structure other than |
make sure cells can't "escape" a context where we know where cyclic operations | ||
are safe. | ||
|
||
</details> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You made great improvements, but the example is still abstract.
- [`GhostCell`](https://plv.mpi-sws.org/rustbelt/ghostcell/paper.pdf) is a | ||
prominent user of this, later slides will touch on it. | ||
|
||
</details> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Next, once we have shown the general idea, I'd suggest transitioning to a concrete API design problem (not an abstract one like here).
- The underlying Branded Data Structure we're going to use here is just a | ||
`Vec<u8>` (the data) and an `InvariantLifetime`. | ||
|
||
- The constructor for this type will take **data for the `Vec<u8`** plus a |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- The constructor for this type will take **data for the `Vec<u8`** plus a | |
- The constructor for this type will take **data for the `Vec<u8>`** plus a |
Expect not much, it's "for" in the sense of "forall" from mathematics. | ||
|
||
- The `for<'a> [trait bound that uses 'a]` binding of `'a` means the lifetime is | ||
"self contained." |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we can explain this in more depth, rather than through an analogy.
get a `AdminToken` to perform administrator actions within a specific | ||
environment (here, a chat client). | ||
|
||
Once the permissions are gained, we can call a `add_moderator` function. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Once the permissions are gained, we can call a `add_moderator` function. | |
Once the permissions are gained, we can call the `add_moderator` function. |
Materials on "token types."
The "Branded tokens" section of this one requires a little extra scrutiny. It may be the case it is too complex for this stage, or that the explanation isn't suitable for the audience/instructors. Either way, I'm eager for input.