Rust Lang Team
      • Sharing URL Link copied
      • /edit
      • View mode
        • Edit mode
        • View mode
        • Book mode
        • Slide mode
        Edit mode View mode Book mode Slide mode
      • Customize slides
      • Note Permission
      • Read
        • Owners
        • Signed-in users
        • Everyone
        Owners Signed-in users Everyone
      • Write
        • Owners
        • Signed-in users
        • Everyone
        Owners Signed-in users Everyone
      • Engagement control Commenting, Suggest edit, Emoji Reply
      • Invitee
    • Publish Note

      Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

      Your note will be visible on your profile and discoverable by anyone.
      Your note is now live.
      This note is visible on your profile and discoverable online.
      Everyone on the web can find and read all notes of this public team.
      See published notes
      Unpublish note
      Please check the box to agree to the Community Guidelines.
      View profile
    • Commenting
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
      • Everyone
    • Suggest edit
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
    • Emoji Reply
    • Enable
    • Versions and GitHub Sync
    • Note settings
    • Engagement control
    • Transfer ownership
    • Delete this note
    • Insert from template
    • Import from
      • Dropbox
      • Google Drive
      • Gist
      • Clipboard
    • Export to
      • Dropbox
      • Google Drive
      • Gist
    • Download
      • Markdown
      • HTML
      • Raw HTML
Menu Note settings Sharing URL Help
Menu
Options
Versions and GitHub Sync Engagement control Transfer ownership Delete this note
Import from
Dropbox Google Drive Gist Clipboard
Export to
Dropbox Google Drive Gist
Download
Markdown HTML Raw HTML
Back
Sharing URL Link copied
/edit
View mode
  • Edit mode
  • View mode
  • Book mode
  • Slide mode
Edit mode View mode Book mode Slide mode
Customize slides
Note Permission
Read
Owners
  • Owners
  • Signed-in users
  • Everyone
Owners Signed-in users Everyone
Write
Owners
  • Owners
  • Signed-in users
  • Everyone
Owners Signed-in users Everyone
Engagement control Commenting, Suggest edit, Emoji Reply
Invitee
Publish Note

Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

Your note will be visible on your profile and discoverable by anyone.
Your note is now live.
This note is visible on your profile and discoverable online.
Everyone on the web can find and read all notes of this public team.
See published notes
Unpublish note
Please check the box to agree to the Community Guidelines.
View profile
Engagement control
Commenting
Permission
Disabled Forbidden Owners Signed-in users Everyone
Enable
Permission
  • Forbidden
  • Owners
  • Signed-in users
  • Everyone
Suggest edit
Permission
Disabled Forbidden Owners Signed-in users Everyone
Enable
Permission
  • Forbidden
  • Owners
  • Signed-in users
Emoji Reply
Enable
Import from Dropbox Google Drive Gist Clipboard
   owned this note    owned this note      
Published Linked with GitHub
Subscribed
  • Any changes
    Be notified of any changes
  • Mention me
    Be notified of mention me
  • Unsubscribe
Subscribe
--- title: "Design meeting 2024-06-26: Match ergonomics (part 3)" tags: ["T-lang", "design-meeting", "minutes"] date: 2024-06-26 discussion: https://rust-lang.zulipchat.com/#narrow/stream/410673-t-lang.2Fmeetings/topic/Design.20meeting.202024-06-26 url: https://hackmd.io/_ey51TmXRqyHq1fQ_t9pmQ --- # Match ergonomics 2024 (part 3) ## Earlier discussions - Part 1: [Design meeting 2024-05-15: Match ergonomics 2024](https://hackmd.io/9SstshpoTP60a8-LrxsWSA) - Part 2: [Design meeting 2023-12-13: Match ergonomics 2024](https://hackmd.io/YLKslGwpQOeAyGBayO9mdw) # Preface ## Draft background Nadri did the original work on revising match ergonomics. Then Jules picked it up, published RFC 3627, and implemented it as an experiment. Now I (TC) have picked it up to build confidence in it, resolve the outstanding concerns, and to better align the proposal with some of our general values, such as minimizing that which is edition-dependent. I've been in communication with Jules and Nadri, but this is my proposal (i.e., the errors are mine). ## Experience report In writing this draft and the associated `match-ergonomics-formality` tool (which is like `a-mir-formality` for match ergonomics), I've done a complete *de novo* ("from scratch") review of the situation, starting back at the original RFC 2005 (match ergonomics). I've read all the threads, and in `match-ergonomics-formality`, I've implemented all of the proposed behaviors, and I've identified how to express these proposals as orthogonal rules. I've carefully analyzed the state transition graphs of each variant. In doing this, I've arrived independently at a proposal that, aside from edition-staging, is more or less identical to Jules' *original* proposal (not the RFC as revised). Interestingly, I arrived here through a different analysis and a different set of motivations than did Jules. This *convergence*, the independent arrival at the same set of rules by two different people in two different ways, gives me some confidence that these rules are actually correct and what we want. ## Meta note on terminology and framing In reviewing the full history, it's become clear to me that the original way that match ergonomics was framed, in RFC 2005, in terms of the default binding mode, is the most sound framing, and that later attempts to frame this in terms of other concepts do not improve clear understanding or support sound language design. Consequently, the text below follows closely to the framing in the original RFC 2005. Throughout this document, we'll refer to the default binding mode as the DBM. # Introduction ## Background Match ergonomics is the feature that lets us write, e.g.: ```rust let [[a]] = &[&&mut &mut &[()]]; let _: &() = a; ``` ...rather than having to write: ```rust let &[&&mut &mut &[ref a]] = &[&&mut &mut &[()]]; let _: &() = a; ``` It was introduced in RFC 2005 and stabilized in 2018. It's been mostly a success. However, in retrospect, some of its rules seem worthy of improvement, and that's what we're discussing. ## Common notation Throughout this document, we define `T` as: ```rust #[derive(Clone, Copy)] struct T; ``` We'll be speaking of both *patterns* and *scrutinees*: ```rust let &[&mut [x]] = &[&mut [T]]; ^^^^^^^^^^^ ^^^^^^^^^^^ Pattern. Scrutinee. ``` ## Binding modes There are three *binding modes* in Rust: ```rust let x = T; //~ `move` binding mode, x: T let ref x = T; //~ `ref` binding mode, x: &T let ref mut x = T; //~ `ref mut` binding mode, x: &mut T ``` I.e., the binding mode controls what we populate into the freshly bound variable. We have explicit syntax for the `ref` and `ref mut` binding modes but not for `move`. For the `move` binding mode, a `copy` will happen instead when `T: Copy`. E.g.: ```rust let &[x] = &[T]; //~ `move` binding mode, x: T ``` I.e., even though we cannot `move` out from behind the shared reference, since `T: Copy`, a `copy` happens. Many of the more interesting examples we'll discuss rely on this. ## Default binding mode (DBM) The *default binding mode* (DBM) determines how variables in patterns are bound when no explicit mode is set. ## How match ergonomics works, high level Match ergonomics allows us to omit in patterns references contained in the type of the scrutinee. E.g.: ```rust let [x] = &[x]; //~ x: &T ``` Prior to match ergonomics, patterns had to *structurally match* in all ways against the type of the scrutinee. I.e., we would have needed to write: ```rust let &[ref x] = &[x]; //~ x: &T ``` Match ergonomics is a *stateful algorithm*. The state we'll be tracking, for all variants, is: 1. The current DBM. 2. Whether or not we're behind a shared reference (or `&` pattern). As we incrementally match the pattern against the scrutinee, according to the rules, we update this state and *reduce* the pattern, the scrutinee, or both. We repeat this process until we arrive at a *binding* or an *error*. The set of combined states are: - `move` - `move_behind_shared_ref` - `ref_mut` - `ref_mut_behind_shared_ref` - `ref` These states are *ordered*. A transition from an earlier state to a later one is called a *forward edge*, from a later one to an earlier one a *backward edge*, and from a state to itself a *self edge*. We call an edge that results in an error an *error edge*. Many of the undesirable properties of today's match ergonomics are due to backward edges. (This may remind us of the benefits of a DAG.) As we reduce patterns and scrutinees in the explanations below, we'll denote the current combined state with a `#[dbm(..)]` attribute. This isn't valid Rust syntax, but it captures the notion that this state is semantically important. E.g., we'll reduce this: ```rust let &[ref mut x] = &[T]; ``` ...to this: ```rust #[dbm(move_behind_shared_ref)] let ref mut x = T; //~^ ERROR cannot borrow as mutable ``` ...which is an error. Note that without this extra state, this would not be an error. I.e., explicitly representing this state is necessary for expressing correct reductions. # Design axioms Here are the properties we want to uphold in the final design: - **Mutability should not affect the DBM**. - Two bindings that differ only by mutability (matched against the same scrutinee) should have the same type. Allowing mutability (i.e. `mut` on a binding) to affect the type of the binding is surprising and undesirable. - **We should avoid setting the DBM to something useless**. - Getting a mutable reference to a value behind a shared reference is useless and always an error. Today's match ergonomics tries to avoid setting the DBM this way but has *loopholes* that leave users with an unhelpful DBM and a confusing error. We should close these loopholes. - **Structural matching should always be allowed**. - The baseline for patterns is always to match structurally against the type of the scrutinee. This should always be allowed anywhere within a pattern, even when combined with match ergonomics, as it is today. In practice, this means preferring structural matches to matches against the DBM itself. - **Minimize backward edges -- the DBM should only move *forward* (or to the same state) except when matching against the DBM itself**. - Backward edges help create the loopholes that allow getting a `ref mut` DBM when behind a shared reference, resulting in a useless binding. - Backward edges make it harder to mentally "follow" what match ergonomics is doing. - We should eliminate these backward edges with one exception. - *Exception*: Allowing backward edges when matching against the DBM itself (and where no structural match is possible) does not create the loophole, is ergonomic, and makes intuitive sense. - **Adding `&` to a pattern should get us `T` if we would otherwise get `&T` (and `&T` if we would otherwise get `&&T`, etc.)**. - If users get a binding of type `&T`, the obvious thing for them to try if they want a binding of type `T` is to add `&` to the pattern. - Similarly, for mutable references, adding `&mut` to a pattern should get us `T` where we'd otherwise get `&mut T`. - This should work when `&`/`&mut` is placed ahead of nested subpatterns, not just when placed ahead of bindings, as that preserves a sense of compositionality. - **Be predicable -- avoid "action at a distance" -- the DBM shouldn't "jump" at the last moment.** - To the degree that the DBM is made visible to users within the pattern, it should be "refined" steadily in the course of matching toward its final value. - It shouldn't visibly be one thing in the pattern but then suddenly "jump" to become something else at the binding. - Upholding this makes it easier for users to follow what's happening and to build the intuitions that RFC 2005 intended. - **Minimize error edges -- Match ergonomics should be *ergonomic*, concise, and convenient**. - Giving an error when it's clear what the user means (and that nothing else could reasonably be meant) is not ergonomic, especially if what the user would have to write instead would be less concise. To the degree that we can do so with simple and consistent rules, we should try to give meaning to what users might write. - **Make the treatment of `&mut` as `&` when appropriate in scrutinees feel consistent**. - In two different ways, today's match ergonomics gives the feeling that it's smartly treating `&mut` as `&` in the scrutinee when that would make sense. - But it's hard for users to predict when this convenience does and doesn't apply, so we should support it consistently. - **Minimize edition-dependent rules**. - We'd like to make the *smallest* number of edition-dependent rule changes. - We'd like *most* improvements to the rules to be applied to all editions. - We'd like to minimize what *must* be decided before Rust 2024. - Adopting more rather than fewer of the rules ahead of Rust 2024 allows for a more ergonomic result after migration, but is not otherwise necessary. # How we explain this (somewhat) concisely With all the proposed rules taken together, we can explain match ergonomics as follows (this assumes we've first explained the concepts of patterns, scrutinees, bindings, and binding modes): - A pattern is matched against a scrutinee, from the outside to the inside, toward one or more bindings. - As we do this matching, incrementally, we *reduce* the pattern, the scrutinee, or both, and update the current default binding mode (DBM). We also keep track of whether we've encountered a shared reference or an `&` pattern. - If a non-reference pattern matches against a reference, then we reduce only the scrutinee. If the scrutinee is a non-reference type and we have a reference pattern that matches the DBM, we reduce only the pattern. Otherwise we reduce both the pattern and the scrutinee on a match. - The DBM is used to set the binding mode of any bindings that aren't marked explicitly with `ref` or `ref mut`; writing `ref` or `ref mut` explicitly always overrides the DBM. - Anywhere within a pattern, it's always valid to match against the structure of the scrutinee at the same level. When you do this, if the DBM was `ref mut` and you had an `&` pattern, the DBM is set to `ref`. Otherwise, the DBM stays the same. - If you omit an `&` in the pattern where the scrutinee has a shared reference, we remove that reference in the scrutinee and set the DBM to `ref`. - If you omit an `&mut` in the pattern where the scrutinee has a mutable reference, we remove that reference. If we've previously encountered a shared reference or an `&` in the pattern, we set or stay in the `ref` DBM. Otherwise we set the DBM to `ref mut`. - If you write `&` or `&mut` in the pattern somewhere that the scrutinee has a non-reference type, we try to match the `&` or `&mut` pattern against the DBM itself (matching a `ref` DBM like a shared reference and a `ref mut` DBM like a mutable reference). On such a match, we set the DBM to `move`. - If you write `&` in the pattern somewhere that `&mut` would match, we allow the match and act as if the mutable reference were actually a shared one (or the `ref mut` DBM had been a `ref` one). Or in other words: [![state transition graph][graph-proposed-2024-svg]][graph-proposed-2024] There's not enough time to explain this graph in detail here, but feel free to ask about this in the questions and we can discuss. # The rules Breaking those pieces down into *orthogonal* elements, and building on top of today's match ergonomics rules, the specific rules we propose to fulfill the design axioms and to achieve the end state explained above are as follows, together with a summary of the motivation for each: - **Rule 1**: When the DBM is not `move` (whether or not behind a reference), writing `mut` on a binding is an error. - This ensures that mutability does not affect the DBM, and that two bindings that differ only by mutability (matched against the same scrutinee) will have the same type. - This rule is edition-dependent. - **Rule 2**: When a reference pattern matches against a reference, do not update the DBM. - This prevents "leakage" back to the `move` DBM that creates a *loophole* through which we can end up setting a useless `ref mut` DBM when behind a shared reference. - This also eliminates this surprising nonlinearity: - We have a binding with a type of `&&T`. - We add a single `&`. - We get a binding of type `T` (rather than `&T`). - ...But only sometimes (specifically, when the DBM is `ref` and a binding matches against a type of `&T`.) - *Rule 1* and *Rule 2* combined eliminate all backward edges from the currently-stable rules, producing (aside from self-edges), a DAG. - This DAG-edness is related to desirable properties of compositionality. - This rule is edition-dependent. - **Rule 3**: If we've previously matched against a shared reference in the scrutinee (or against a `ref` DBM under *Rule 4*, or against a mutable reference treated as a shared one or a `ref mut` DBM treated as a `ref` one under *Rule 5*), set the DBM to `ref` whenever we would otherwise set it to `ref mut`. - This closes *all* remaining loopholes that would allow getting a useless (or otherwise unwarranted) `ref mut` DBM. - This helps to make the treatment of `&mut` as `&` (when appropriate) in scrutinees feel more consistent. - This rule can be applied to *all* editions. - **Rule 4**: If an `&` or `&mut` pattern is being matched against a non-reference type **and if** the DBM is `ref` or `ref mut`, match the pattern against the DBM as though it were a type. - This ensures that anywhere that a binding would have a type of `&T` (or `&mut T`), that adding an `&` (resp., `&mut`) ahead of that binding will produce a binding of type `T`. - This works too, in the same way, when `&` (or `&mut`) is placed ahead of a subpattern. - Because we apply the rule *only* when matching against a non-reference type, we preserve and prioritize structural matching. - This rule achieves the ergonomic win that the rule being reversed in *Rule 2* was meaning to provide, but in a more comprehensive, consistent, and predicable way. - This rule is a pure ergonomic *convenience*, but that's what match ergonomics is all about. - Of the set, this rule is the most odd in two ways: - This rule makes the DBM *observable* within the pattern, which commits us to certain details about the operation of the currently-stable rules and about the operation of *Rule 3*. - This rule adds two backward edges (or three when combined with *Rule 5*); however, the particulars make these unobjectionable. - The ergonomic win seems clearly worth it as compared with these nits. - This rule can be applied to *all* editions. - The fact that we only apply the rule when matching against a non-reference type is what, in particular, makes this true. - **Rule 5**: If an `&` pattern is being matched against a mutable reference type (or against a `ref mut` DBM under *Rule 4*), act as if the type were a shared reference instead (or that the `ref mut` DBM is a `ref` DBM instead). - This helps to make the treatment of `&mut` as `&` (when appropriate) in scrutinees feel more consistent. - This rule is a pure ergonomic *convenience*, but that's what match ergonomics is all about. - This helps references to get out of the way, which was the original motivation for RFC 2005. - There's no other reasonable thing that this could mean. - This rule can be applied to *all* editions. # Explanation ## Rule 1 Today's Rust produces this oddity: ```rust let (mut x, y) = &(T, T); //~ mut x: T, y: &T ``` That is, making the binding mutable affects the *type* of the binding. This happens because adding `mut` to the binding resets the DBM to `move`. There had once been discussion of allowing the binding mode to be set explicitly with the `move` keyword like `ref` and `ref mut` can be. `mut` is currently a bit like this, but it's oddly coupled with mutability. This is just surprising. Under *Rule 1*, we would make it a hard error to write `mut` if the DBM isn't already `move`. This prevents `mut` from resetting the DBM. Users (who want the result above) could write instead: ```rust let (x, y) = &(T, T); //~ x: &T, y: &T let mut x = *x; //~ mut x: T ``` ...or (being explicit): ```rust let &(mut x, ref y) = &(T, T); //~ mut x: T, y: &T ``` There is *some* appeal in the behavior (which is presumably why it was done), but those ergonomic wins can be recaptured in a more comprehensive, consistent, and less surprising way with *Rule 4*. Under that rule, users could also equivalently write: ```rust let (&(mut x), y) = &(T, T); //~ mut x: T, y: &T // ^ Matches against the `ref` DBM and sets a `move` DBM. ``` ## Rule 2 ### Rule 2: Part 1 Today's Rust produces this oddity: ```rust let (&x, y) = &(&T, &T); //~ x: T, y: &&T ``` That is, there's a *nonlinearity*. Adding a single `&` to the pattern did more than we might expect. "Interesting...", you say to yourself, "adding an `&` ahead of the binding 'cancels out' any references in the type?" But no, that's not quite correct, as we can see: ```rust let [&x] = &[T]; //~^ ERROR mismatched types ``` Similarly: ```rust let [&x] = [&&T]; //~ x: &T ``` What's *really happening* is that when an `&` or `&mut` pattern matches against a reference in the scrutinee, we reset the binding mode to `move`. That's kind of subtle, as shown above, and *Rule 2* proposes that we stop doing that. Users (who want the result above) could instead write: ```rust let (x, y) = &(&T, &T); //~ x: &&T, y: &&T let x = **x; //~ x: T ``` ...or (being explicit): ```rust let &(&x, ref y) = &(&T, &T); //~ x: T, y: &&T ``` Again, the behavior being removed here had *some* appeal (which is why it was done), but we can better capture those ergonomic wins with *Rule 4*. Under that rule, users could write: ```rust let (&&x, y) = &(&T, &T); //~ x: T, y: &&T // ^^ Matches against the `ref` DBM. // | // ^ Matches structurally. ``` ### Rule 2: Part 2 Today's match ergonomics goes out of its way to give a reference binding when a mutable reference binding would be useless. E.g.: ```rust let [x] = &mut &[T]; //~ x: &T let [x] = &&mut [T]; //~ x: &T let [[x]] = &mut [&[T]]; //~ x: &T let [[x]] = &[&mut [T]]; //~ x: &T ``` What's happening here, precisely, is that when we're already in the `ref` DBM state, we *stay* in the `ref` DBM (rather than moving to the `ref mut` DBM) when a non-reference pattern matches against a mutable reference in the scrutinee. However, there are currently "loopholes" that allow us to "leak" from the `ref` DBM back into the `ref mut` DBM, even though that's always useless. E.g.: ```rust let [&mut [x]] = &[&mut &mut [T]]; //~^ ERROR cannot borrow as mutable ``` What's happening here is that, first, we set a `ref` DBM due to the missing outer `&` in the pattern, then we match the `&mut` pattern against the first mutable reference. That puts us in the `move` DBM. Since we've leaked out of the `ref` DBM, Rust now allows the `ref mut` DBM to be set when we pass that second mutable reference, and that becomes the final binding mode. When you stare at this long enough, it becomes clear this is just a bug. The intention of RFC 2005, given the `ref` stays `ref` rule, was to avoid `ref` -> `ref mut` transitions. The fact that it left open `ref` -> `move` -> `ref mut` transitions was probably an oversight. *Rule 2* fixes this by preventing the transition to `move` that creates the leak. The cases that benefit from transitions to `move` are covered in *Rule 4* in a way that doesn't introduce the leak. ### Rule 2: Part 3 Today's match ergonomics does this: ```rust let [&x] = &mut [&T]; //~ x: T ``` There are two possible interpretations for this behavior: 1. The `&` in the pattern reset the DBM to `move`. 2. The `&` in the pattern matches against *both* the inner shared reference and *either* an outer shared reference or an outer mutable one. As we've discussed, the first intuition is the best one. However, people could be forgiven for thinking the second, and this second intuition is one way in which we give people the *feeling* today that mutable references in scrutinees sometimes coerce to shared references. We'll come back to this in *Rule 5*. ## Rule 3 ### Rule 3: Part 1 Recall again that we go out of our way, today, to give people `ref` bindings when `ref mut` bindings would be useless, e.g.: ```rust let [x] = &mut &[T]; //~ x: &T let [x] = &&mut [T]; //~ x: &T let [[x]] = &mut [&[T]]; //~ x: &T let [[x]] = &[&mut [T]]; //~ x: &T ``` In *Rule 2*, we talked about how there are certain *loopholes* to this. *Rule 2* closes some of those loopholes. This rule, *Rule 3*, closes the rest. Consider, today, e.g.: ```rust let &[x] = &&mut [T]; //~^ ERROR cannot borrow as mutable ``` What's happening here is that the `&-&` match leaves us in the `move` DBM, then the `[]-&mut` match puts us in the final `ref mut` DBM. But since we're behind that shared reference, this is an error. *Rule 2* doesn't close this part of the loophole because this problem isn't that we leaked out of `ref`, but that we never ended up there. This is what *Rule 3* fixes. It causes us to set the DBM to `ref` rather than to `ref mut` whenever we're behind a shared reference or `&` pattern. ### Rule 3: Part 2 As we discussed under *Rule 2*, today, this works: ```rust let [&x] = &mut [&T]; //~ x: T ``` Once we apply *Rule 2*, this becomes another loophole to get `ref mut` behind a shared reference, and we'll get an error: ```rust let [&x] = &mut [&T]; //~ ERROR cannot borrow as mutable ``` Here, the `[]-&mut` match puts us in the `ref mut` DBM, then the `&-&` match keeps us there. *Rule 3* fixes this also, by pushing us into the `ref` DBM on the `&-&` match, and we get what we want: ```rust let [&x] = &mut [&T]; //~ x: &T ``` ## Rule 4 ### Rule 4: Part 1 As we touched on above, sometimes it's convenient to be able to get to a `move` DBM. The behaviors that we're reversing in *Rule 1* and *Rule 2* were trying to get at this convenience, but they had unfortunate side effects. *Rule 4* is a principled solution to this ergonomic ask. Let's say that we have: ```rust let [x] = &[T]; // x: &T ``` That is, we get a binding of type `&T`. Let's say that we want `T` instead (and `T: Copy`). The obvious and natural thing to do would be to add an `&` ahead of the binding, e.g.: ```rust let [&x] = &[T]; // x: T ``` With *Rule 4*, this works. This is particularly helpful when there are multiple bindings in play, e.g.: ```rust let (&x, y) = &(T, T); // x: T, y: &T ``` ...as it means that we don't have to write the more explicit form: ```rust let &(x, ref y) = &(T, T); // x: T, y: &T ``` ### Rule 4: Part 2 It's important to note that we apply *Rule 4* in such a way that we preserve the primacy of structural matching. That is, we always look to match first structurally. Only when we have a reference pattern that's being matched against an non-reference type do we try to match that reference pattern against the DBM itself. This allows patterns such as the following to work: ```rust let [&mut [x]] = &[&mut [T]]; //~ x: &T ``` We consider this important. The baseline of pattern matching is matching structurally. ### Rule 4: Part 3 There's one thing about *Rule 4* that makes it stand out, as compared to the other rules, that's worth mentioning. This rule makes the operation of the other rules more *observable*. Consider this code that works today: ```rust let [[x]] = &&mut [[T]]; //~ x: &T ``` We can see that the DBM ends up as `ref`, but we could ask, *at how many* points along the matching process is it `ref`? That is, did we update the DBM to `ref` as we went along (as this document has described), or did we updated it at the last moment, right before the binding? *Rule 4* commits us to an answer (we update the DBM as we go along) because it makes this work: ```rust let [&[x]] = &&mut [[T]]; //~ x: T // ^ The DBM is observable here. ``` We'll discuss this more below, but suffice it to say we think this is fine. It's just worth pointing out. ## Rule 5 ### Rule 5: Part 1 There are all kinds of ways in which we *seem* to coerce mutable references in the scrutinee to shared references during pattern matching. E.g. this: ```rust let [&x] = &mut [&T]; //~ x: T (today) ``` Or these: ```rust let [x] = &mut &[T]; //~ x: &T let [x] = &&mut [T]; //~ x: &T let [[x]] = &mut [&[T]]; //~ x: &T let [[x]] = &[&mut [T]]; //~ x: &T ``` And once we adopt the other rules here, this one works too: ```rust let [&&x] = &mut [&T]; //~ x: T ``` The first `&` in the pattern matches structurally. At that point, we're behind a shared reference, so according to *Rule 3*, we switch the DBM to `ref`. Then, under *Rule 4*, the remaining `&` matches against the `ref` DBM. The point is, no matter how much we try to explain it, it's always going to seem odd if this works: ```rust let [&x] = &mut [&T]; //~ x: &T (proposed) ``` ...but this does not: ```rust let &x = &mut &T; //~ ERROR mismatched types ``` So *Rule 5* makes this and similar cases work too, i.e.: ```rust let &x = &mut &T; //~ x: &T ``` This is just an ergonomic convenience, but that's what match ergonomics is about, after all. It makes the whole package *feel* a bit more consistent. ### Rule 5: Historical note An earlier proposal (RFC 3627) had motivated this rule in part to partially paper over the ways in which that proposal broke the primacy of structural matching. In this proposal, we preserve the primacy of structural matching (by not adopting the "Rule 4 early" variant we'll discuss in the alternatives), so we don't need the rule for this reason. Still, *Rule 5* seems justifiable on its own merits. # Alternatives ## Do Rule 1 and Rule 2 only for now Of the rules laid out above, only *Rule 1* and *Rule 2* are edition-dependent. The other rules only act on cases that today result in errors (inclusive of borrow checker behavior). This means that, strictly speaking, we only need to decide on *Rule 1* and *Rule 2* today. If we get blocked on the others, but we like *Rule 1* and *Rule 2*, that's what we should do, and the rest of the rules can be stacked on later. What we would lose in doing that, during the migration to Rust 2024, is that somewhat more code would need to be migrated to a fully explicit form rather than to another ergonomic form. ## Don't do Rule 5 *Rule 5* makes `&` patterns match against mutable references (and against the `ref mut` DBM under *Rule 4*), which makes things feel consistent with the other places where we kind of *seem* to do that. It's a pure ergonomic convenience. We could not do it. But it's obvious what it should mean and that it can't reasonably mean anything else. And match ergonomics is really all about ergonomic conveniences, so it seems like a win. ## Don't do Rule 4 *Rule 4* is a bit unusual, among the set, in that it makes more visible the operation of the other rules, particularly those of *Rule 3* and *Rule 5*. E.g., due to *Rule 4*, we can tell what the DBM is at the marked point: ```rust let [&[x]] = &&mut [[T]]; //~ x: T // ^ The DBM is observable here. ``` Without *Rule 4*, we could kind of pretend that the DBM is whatever we want within most of the pattern as long as it "jumps" into the correct value at the binding. However, we don't really see the point of playing this kind of pretend with the DBM. It's much more natural to the spirit of RFC 2005 for the DBM to be smoothly updated as we go along matching the pattern against the scrutinee. So we don't have any concerns about this exposure of the DBM within the pattern and think this is fine. ## Do "Rule 4 early" variant The current draft of Jules' RFC proposes layering another rule on top of *Rule 4* (not in this proposal) that we call the "Rule 4 early" variant. That rule is: > If the DBM is `ref` or `ref mut`, match a reference pattern against the DBM as though it were a type *before* considering the scrutinee. By contrast, without this, *Rule 4* only matches against the DBM when, given a reference pattern, the scrutinee is a *non-reference* type (and therefore could not match against the pattern). The effect of this early variant is to break the primacy of structural matching. E.g., with that variant, this does not work, even though the `&mut` in the pattern exactly matches the structure in the scrutinee: ```rust let [&mut [x]] = &[&mut [T]]; //~^ ERROR mismatched types ``` This happens because, at that point, we're in the `ref` DBM, and so the `&mut` pattern can't match against it, but it tries to due to this early rule. We think the primacy of structural matching is important, so we haven't adopted this variant. Further, this variant adds even more backward edges than *Rule 4* already does, and we're trying to minimize those. There's also a practical problem with this variant also; while *Rule 4* is backward compatible and can be applied to all editions, the early variant is not (because it breaks structural matching). This means that it could only happen over an edition. Since *Rule 4* (in any form) makes the operation of the other rules visible, this could also then make *Rule 3* and probably *Rule 5* edition-dependent. That's kind of a mess. ## Do "Rule 3 lazy" variant As we recall, *Rule 3* is the rule that has us set the `ref` DBM in place of the `ref mut` DBM whenever we're behind a shared reference or `&` pattern. As proposed, we apply that rule as we go along matching the pattern against the scrutinee. However, conceivably, we could instead update the DBM as though we *weren't* applying this rule, then when we reach the binding, switch the DBM to what we *would* have set it to under *Rule 3*. That is, we could apply *Rule 3* lazily. We think part of the reason people may have proposed this was due to the way that the early variant of *Rule 4* (proposed in the RFC) broke structural matching (which this proposal doesn't do). The other reason it's been proposed is to make this sort of thing work: ```rust let &[[&mut x]] = &[&mut [T]]; //~ x: T ``` What's going on here is that the structural match of the outer `&` leaves us in the `move` DBM, we then pass a mutable reference in the scrutinee, which *would* put us into the `ref` DBM (under *Rule 3*), but instead we set the `ref mut` DBM (under the lazy variant), so that then, when we hit the `&mut` in the pattern, it matches under *Rule 4* against the `ref mut` DBM. We think this just promotes confusion about how the default binding mode works, and this topic is already hard enough. Using structural matching, people could just write: ```rust let &[&mut [x]] = &[&mut [T]]; //~ x: T ``` Or, using our rules, they could write more concisely: ```rust let [[&x]] = &[&mut [T]]; //~ x: T ``` And that's more clear, and it's in keeping with the design axioms we've laid out (here, e.g., that without the `&` in the pattern, the user would get `&T`, and adding `&` gets the user a `T`.) ## Do the "Spin Rule" The "spin rule" is something else people have proposed. It goes along with the lazy variant of *Rule 3*. It proposes that, when we're in the `ref` DBM and we match a non-reference pattern against a mutable reference that we "spin" back into the `ref mut` DBM. Similar to the above, the motivation is to make things like this work: ```rust let [[&mut x]] = &[&mut [T]]; //~ x: T ``` Here, once we pass that shared reference we end up in the `ref` DBM. Normally we'd stay there. But under the "spin rule", when we pass the `&mut`, we "spin" back into the `ref mut` DBM. That allows us to match the `&mut` pattern against the `ref mut` DBM. As above, we believes this promotes only confusion. It relies on the lazy variant of *Rule 3* to flip these DBMs back into something useful right at the binding. Further, this rule creates more of the backward edges we're trying to get rid of. In reality, people will just write: ```rust let [[&x]] = &[&mut [T]]; //~ x: T ``` ...and be happy. Because they originally wrote: ```rust let [[x]] = &[&mut [T]]; //~ x: &T ``` ...then decided that they wanted a `T` rather than a `&T`, and added the `&`: ```rust let [[&x]] = &[&mut [T]]; //~ x: T ``` # Conclusion We propose Rules 1-5, and that the proposed FCP on RFC 3627 be updated to reflect the adoption of these rules (with the RFC to be updated to incorporate them). We proposed that *Rule 1* and *Rule 2* become part of Rust 2024. And we propose that *Rule 3*, *Rule 4*, and *Rule 5* be adopted in all editions. # Links [graph-proposed-2024]: https://mermaid.live/view#pako:eNp9VcuO4jAQ_BXLEnBppPgRXoc9DYeRNjlsOFga5pABM0SQZOUJs4sQ_76dQEKMrQWE3JVydae6nVxoeqrK5Fxs6GKXHr800E251XRBB4MLyYqsWpALGe2O5Z_NPjXVqAk3J_OtcTk6ZoVOzYhc6-9gsC7WRUclP3_V8evbmiYVxmv6vi6WGC2NKU0TRRjl5bdugvgekA-9z4otGTaoQtToHclPN4HkFjdrFCfWZzz-QaIaj3BJ3t7Hwwee1HjixTsUk7joyuUOWwEbxe03jSfuTdbDbdFlx131dOO64vh_d9KorJ5V7ume0LuJHtS9k7-Wo4lVh-VR7HoU31Hbow61POpxex7Z3N59xK5Hyz539YzaTrS6jhOx14nYdSK2ZuuhoWpceTulvGzl-taizmwp17ce154t5fVNeWdLub5ZZ2dlnanI29PIra3HtXsaeWuLvD2NvD2N3J622eyePvrhnAXlZSvvBCh3AlTnkMOOXHbjJz4jD0l1PmoSAAOGfxPg-JsBn4PgICSIOUgGcgJyDiEjX5UpD3rxabS29iN9AmLaXv84nnT_MkqBhBAmMAUmgYXAcDEDNgceAJfApyACEAyEADEDGYAMQc4gDCDkj6zpua-K-7FqDkwAZ8A5cFyEIHAnB4kZJciupNKkxScWRYHm2uRptqWLC632Oq9fKdvUHOgV6On3Nq30S5Zirvz-3rn-A6UOog8 [graph-proposed-2024-svg]: https://mermaid.ink/img/pako:eNp9VcuO4jAQ_BXLEnBppPgRXoc9DYeRNjlsOFga5pABM0SQZOUJs4sQ_76dQEKMrQWE3JVydae6nVxoeqrK5Fxs6GKXHr800E251XRBB4MLyYqsWpALGe2O5Z_NPjXVqAk3J_OtcTk6ZoVOzYhc6-9gsC7WRUclP3_V8evbmiYVxmv6vi6WGC2NKU0TRRjl5bdugvgekA-9z4otGTaoQtToHclPN4HkFjdrFCfWZzz-QaIaj3BJ3t7Hwwee1HjixTsUk7joyuUOWwEbxe03jSfuTdbDbdFlx131dOO64vh_d9KorJ5V7ume0LuJHtS9k7-Wo4lVh-VR7HoU31Hbow61POpxex7Z3N59xK5Hyz539YzaTrS6jhOx14nYdSK2ZuuhoWpceTulvGzl-taizmwp17ce154t5fVNeWdLub5ZZ2dlnanI29PIra3HtXsaeWuLvD2NvD2N3J622eyePvrhnAXlZSvvBCh3AlTnkMOOXHbjJz4jD0l1PmoSAAOGfxPg-JsBn4PgICSIOUgGcgJyDiEjX5UpD3rxabS29iN9AmLaXv84nnT_MkqBhBAmMAUmgYXAcDEDNgceAJfApyACEAyEADEDGYAMQc4gDCDkj6zpua-K-7FqDkwAZ8A5cFyEIHAnB4kZJciupNKkxScWRYHm2uRptqWLC632Oq9fKdvUHOgV6On3Nq30S5Zirvz-3rn-A6UOog8?type=svg --- # Discussion ## Attendance - People: TC, nikomatsakis, Josh, pnkfelix, scottmcm, tmandry, Jules Bertholet, Nadri, Xiang ## Meeting roles - Minutes, driver: TC ## Clarification around "spin rule" example nikomatsakis: The text gives this example that motivates the "spin rule"... ```rust let [[&mut x]] = &[&mut [T]]; //~ x: T ``` ...but it doesn't say what the rules proposed in the document will do in this situation. Let me see if I can figure it out. * `[[&mut x]] = &[&mut [T]]]` &there4; We enter into ref DBM * `(ref) [[&mut x]] = [&mut [T]]` &there4; structural match * `(ref) [&mut x] = &mut [T]` &there4; pass through &mut, remain in ref DRM * `(ref) [&mut x] = [T]` &there4; structural match * `(ref) &mut x = T` &there4; error? Can `&mut` pattern match against `ref` DRM? TC: Tool output: ``` >> set proposed >> explain #[dbm(move)] let [[&mut x]] = &[&mut [T]]; ^ ^ //~^ NOTE matches []-& (non-reference pattern matches against shared reference) >> #[dbm(ref)] let [[&mut x]] = [&mut [T]]; ^ ^ //~^ NOTE matches []-T (non-reference pattern matches against some non-reference type) >> #[dbm(ref)] let [&mut x] = &mut [T]; ^ ^^^^ //~^ NOTE matches []-&mut (non-reference pattern matches against mutable reference) //~| NOTE sets DBM to `ref` eagerly behind reference >> #[dbm(ref)] let [&mut x] = [T]; ^ ^ //~^ NOTE matches []-T (non-reference pattern matches against some non-reference type) >> #[dbm(ref)] let &mut x = T; ^^^^ ^ //~^ ERROR //~| NOTE matches &mut-T (`&mut` pattern matches against some non-reference type) ``` NM: Makes sense. I'm satisfied. Thank you. tmandry: As further reason why this makes sense, is that if you didn't have the `&mut` there, you'd have a shared reference. ## Explicit `move` mode Josh: Because the DBM is a single state, not a stack, you can write things like `let [&x] = &&&&&[T];`. However, in general, this still feels like it makes moving the *hard* case where you have to mentally track whether or not you've fallen into a non-`move` DBM and undo it with `&`, whereas you can arbitrarily mismatch the *number* of references and still end up in `ref` mode. You have to walk the "move" tightrope, and know whether you've fallen off into "ref" or if you've stayed in "move", because at the end you need to know which one you're in to write `x` or `&x`. Because of this, I find myself wishing for the ability to write `let [move x] = &&&&&[T];`. This would have the property that, unlike `&x`, you can *always* write `move x`, whether you're in `ref` mode or `move` mode: `let [move x] = [T]` would *also* work, whereas `let [&x] = [T]` is (correctly) an error. This isn't an immediate need, but it's something that feels like it'd make it easier to cope with match ergonomics. This also seems like a preferable alternative to: ```rust let (&(mut x), y) = &(T, T); // instead: let (move mut x, y) = &(T, T); // (or `mut move`? Unsure.) ``` Though I would maintain that in practice this is still better: ```rust let &(mut x, ref y) = &(T, T); ``` NM: This would be a backward compatible extension. ## Matching `&mut` pattern against `&` value Jules: Under this proposal, this is allowed: ```rust let [&mut [x]] = &[&mut [T]]; ``` Is this? Should it be? ```rust let &mut x = &&mut T; ``` TC: Yes, on the first example, that should be allowed (the second example is not). This is part of what the document calls "preserving the primacy of structural matching". TC: By the way, here's how the tool reduces the first example: ``` >> set proposed >> explain #[dbm(move)] let [&mut [x]] = &[&mut [T]]; ^ ^ //~^ NOTE matches []-& (non-reference pattern matches against shared reference) >> #[dbm(ref)] let [&mut [x]] = [&mut [T]]; ^ ^ //~^ NOTE matches []-T (non-reference pattern matches against some non-reference type) >> #[dbm(ref)] let &mut [x] = &mut [T]; ^^^^ ^^^^ //~^ NOTE matches &mut-&mut (`&mut` pattern matches against mutable reference type) //~| NOTE apply Rule 2: preserve DBM on `&-&`/`&mut-&mut` match >> #[dbm(ref)] let [x] = [T]; ^ ^ //~^ NOTE matches []-T (non-reference pattern matches against some non-reference type) >> #[dbm(ref)] let x = T; ^ ^ //~^ NOTE matches x (binding pattern) >> let ref x = T; //~ x: &T ^^^^^ ^ //~^ NOTE DBM applied //~| NOTE matches x (binding pattern) ``` Jules: Actually, I agree with doing it this way. I hadn't considered allowing the first but disallowing the second. ## Rule 4 too strict? Jules: ```rust let [&mut x] = &mut [&T]; ``` `&mut` pattern does not match `&` value, but value is reference so rule 4 fails to apply, so this errors? TC: In that example, here's how the tool reduces it: ``` >> set proposed >> explain #[dbm(move)] let [&mut x] = &mut [&T]; ^ ^^^^ //~^ NOTE matches []-&mut (non-reference pattern matches against mutable reference) >> #[dbm(ref_mut)] let [&mut x] = [&T]; ^ ^ //~^ NOTE matches []-T (non-reference pattern matches against some non-reference type) >> #[dbm(ref_mut)] let &mut x = &T; ^^^^ ^ //~^ ERROR //~| NOTE matches &mut-& (`&mut` pattern matches against shared reference type) ``` ...and I agree with that reduction. TC: It's not a violation of *Rule 4* because *Rule 4* specifies that it only applies when we match a reference pattern against a non-reference type in the scrutinee. Jules: so you are more restrictive here than current RFC, which allows this. What is the motivation? TC: Actually, I wouldn't say it's more restrictive. Applying *Rule 4 early* disallows code too. The motivation of not adopting *Rule 4 early* is that it breaks the primacy of structural matching. Jules: I mean, it's not *strictly* more permissive. Why not allow the union of both? TC: That is what's discussed in the document as the alternative, which is to apply *Rule 4 early* in addition to *Rule 4*. But this still breaks the primacy of structural matching, because if we're willing to preferentially match against the DBM when a reference pattern is being compared against a reference type, then we run into cases where structural matching doesn't work, because we're trying to match a `&mut` pattern against a `ref` DBM. I suppose we could make that work somehow, but... it starts getting hairy to me. Jules: not preferentially, only as fallback if structural match fails. Is that not all we need? TC: Have an example? Jules: (Alt version of prev reduction) ```! >> #[dbm(ref_mut)] let &mut x = &T; ^^^^ ^ //~^ NOTE matches &mut-& (`&mut` pattern matches against shared reference type): FAIL //~| Fallback: `&mut` pattern matches against `ref mut` dbm -> success! #[dbm(move)] let x = &T; ``` TC: Seems backward compatible if we later wanted to do that. Jules: Why wait then? I don't like that current proposal makes stuff work in all cases except when scrutinee is a reference. `&` and `&mut` should be 1st-class values like any other. See also deref patterns below ```rust let T = 3; // (or `true`, `()`, etc) let [&mut x] = &mut [T]; // works let T = &3; let [&mut x] = &mut [T]; // fails let T = "hi"; let [&mut x] = &mut [T]; // also fails ``` NM: To some extent, the point of match ergonomics is to deemphasize references within patterns. So this may just be the point. Nadri: ```rust! let [x] = &mut [&T]; // x: &mut &T let [&mut x] = &mut [&T]; // ? fn foo<T: Copy>(x: T) { let [&mut s] = &mut [x]; // x: T } fn foo(x: &u8) { let [&mut s] = &mut [x]; // ERROR } ``` TC: ```rust! let [&mut x] = &mut [&T]; //~^ ERROR today ``` Nadri: I also want that `x: &mut T` => I can write `&mut x` in the pattern to copy out. NM: Clarification, under Rule 4 as proposed here... ```rust let T = &3; let [&x] = &mut [T]; // x: &T ``` Nadri: I prefer this over "primacy of structural matching". We can say "primacy of structural matching except for references". We're treating references specially anyway. pnkfelix: Could we get both? Jules: I'm arguing there is by falling back and taking the union. pnkfelix: That seems to imply backtracking? Jules: It'd be backtracking to depth 1. ## Tracking rule approvals (running notes) Josh: - Rule 1: :+1: - Rule 2: :+1: - Rule 3: :+1: - Rule 4: :+1: with some trepidation. I agree it's better than current behavior, I'm hesitant to say it's better than being explicit. - Rule 5: :+1: - Reject "Rule 4 early": :+1: - Reject "Rule 3 lazy": :+1: - Reject "spin rule": :+1: ## Interaction with deref patterns Nadri: First, damn. Thanks for doing this impressive work TC (and Jules too). Nadri: I'm worried about forward compatibility with deref patterns, depending on the specific design choices we end up with. Particularly if we choose `&<pat>` for deref patterns. TC: My general feeling, high level, on that is thaht deref patterns should be designed in a way compatible with this. That is, the rules here seem compelling enough that it seems a good guiding principle for other designs. Nadri: deref patterns don't have a lot of moving parts; it's almost exclusively syntax. Nadri: I'm thinking that rule 4 would be a forward-compat hazard. ```rust! // With rule 4: let [&mut [x]] = &[&mut [T]]; // x: &T let [&mut [x]] = &[MyBox::new([T])]; // ??? ``` Nadri: What if `MyBox` adds the `DerefPure` impl after the fact? Could it break existing code? I'm struggling to think this through. TC: The first line there doesn't rely on *Rule 4*. Nadri: oh, I copy-pasted the wrong example from that section x) TC: If it helps, here's the reduction of that case: ``` >> set proposed >> explain #[dbm(move)] let [&mut [x]] = &[&mut [T]]; ^ ^ //~^ NOTE matches []-& (non-reference pattern matches against shared reference) >> #[dbm(ref)] let [&mut [x]] = [&mut [T]]; ^ ^ //~^ NOTE matches []-T (non-reference pattern matches against some non-reference type) >> #[dbm(ref)] let &mut [x] = &mut [T]; ^^^^ ^^^^ //~^ NOTE matches &mut-&mut (`&mut` pattern matches against mutable reference type) //~| NOTE apply Rule 2: preserve DBM on `&-&`/`&mut-&mut` match >> #[dbm(ref)] let [x] = [T]; ^ ^ //~^ NOTE matches []-T (non-reference pattern matches against some non-reference type) >> #[dbm(ref)] let x = T; ^ ^ //~^ NOTE matches x (binding pattern) >> let ref x = T; //~ x: &T ^^^^^ ^ //~^ NOTE DBM applied //~| NOTE matches x (binding pattern) ``` Nadri: this tool is really cool. Do you get my high-level point though? Nadri: essentially, any rule that relies on "the scrutinee is not a reference" might want to apply if the scrutinee is a smart pointer, hence adding `DerefPure` could change behavior. Nadri: One design axioms I'd like for deref patterns is "Smart pointers get out of the way: the ergonomic distance between using a reference and a smart pointer is as small as necessary". Is that incompatible with some of this proposal? Jules: My suggestion above adresses this. TC: The high level point was: > essentially, any rule that relies on "the scrutinee is not a reference" might want to apply if the scrutinee is a smart pointer, hence adding `DerefPure` could change behavior. The only rule proposed that acts when the scrutinee is not a reference is *Rule 4* and it only acts against the DBM... would have to think about this. ## Limiting observability of DBM with Rule 4 variant tmandry: It seems like the purpose of Rule 4 is mainly to allow putting an ampersand in front of a binding name to undo a level of reference. Can we restrict it to that case by only allowing dbm matches in front of a binding name? ```rust! let [&[x]] = &&mut [[T]]; //~ Error let [[&x]] = &&mut [[T]]; //~ x: T let &&mut [[x]] = &&mut [[T]]; //~ x: T ``` That would make certain less-common patterns less convenient ```rust let [&(x, y)] = &[(T, T)]; //~ Error let [(&x, &y)] = &[(T, T)]; // x: T, y: T ``` ## Naming the rules tmandry: My attempt at concise names for each rule: Rule 1: `mut x` on move dbm only Rule 2: structural `&x` does not reset dbm Rule 3: behind_shared_ref Rule 4: match `&x` against dbm, but prefer structural matches Rule 5: allow `&x` against mutable refs ## Review on design axioms and rules tmandry: I agree with the rules and the design axioms proposed. I'm in agreement with the rules 1-5 as proposed. NM: I'm pretty aligned with what tmandry said. I also think Jules and Nadri raise an interesting point and will need to think about that further. I found the document compelling and the vast majority made total sense. Looking at more transitions could help. I'm happy to move forward overall but keep talking about Jules/Nadri's point. pnkfelix: I'm on the same page as Niko. I'm thinking about the bit about the primacy of structural matching. (its "obviously right" when applied to 100% of the pattern; the subtleties arise from trying to figure out how it should correctly compose when it appears within a subpattern when match-ergonomics are already in play) Josh: I'm +1 on Rules 1-5 and +1 on rejecting the rules rejected. I feel like Rule 4 is better than the current behavior; it's nice to have an option to switch back to `move` mode. But I'm hesitant to say that it's better than having an explicit way to do that, or not using match ergonomics at all. But it's an improvement, so I don't want to block it. scottmcm: These feel like reasonable rules to me. I'm happy to yield to the people who have dug into this more. tmandry: To add to what I said: I think we need to understand which (potentially desirable) possibilities we are ruling out before adopting each rule, and I think most of the uncertainty here is around rule 4.

Import from clipboard

Paste your markdown or webpage here...

Advanced permission required

Your current role can only read. Ask the system administrator to acquire write and comment permission.

This team is disabled

Sorry, this team is disabled. You can't edit this note.

This note is locked

Sorry, only owner can edit this note.

Reach the limit

Sorry, you've reached the max length this note can be.
Please reduce the content or divide it to more notes, thank you!

Import from Gist

Import from Snippet

or

Export to Snippet

Are you sure?

Do you really want to delete this note?
All users will lose their connection.

Create a note from template

Create a note from template

Oops...
This template has been removed or transferred.
Upgrade
All
  • All
  • Team
No template.

Create a template

Upgrade

Delete template

Do you really want to delete this template?
Turn this template into a regular note and keep its content, versions, and comments.

This page need refresh

You have an incompatible client version.
Refresh to update.
New version available!
See releases notes here
Refresh to enjoy new features.
Your user state has changed.
Refresh to load new user state.

Sign in

Forgot password

or

By clicking below, you agree to our terms of service.

Sign in via Facebook Sign in via Twitter Sign in via GitHub Sign in via Dropbox Sign in with Wallet
Wallet ( )
Connect another wallet

New to HackMD? Sign up

Help

  • English
  • 中文
  • Français
  • Deutsch
  • 日本語
  • Español
  • Català
  • Ελληνικά
  • Português
  • italiano
  • Türkçe
  • Русский
  • Nederlands
  • hrvatski jezik
  • język polski
  • Українська
  • हिन्दी
  • svenska
  • Esperanto
  • dansk

Documents

Help & Tutorial

How to use Book mode

Slide Example

API Docs

Edit in VSCode

Install browser extension

Contacts

Feedback

Discord

Send us email

Resources

Releases

Pricing

Blog

Policy

Terms

Privacy

Cheatsheet

Syntax Example Reference
# Header Header 基本排版
- Unordered List
  • Unordered List
1. Ordered List
  1. Ordered List
- [ ] Todo List
  • Todo List
> Blockquote
Blockquote
**Bold font** Bold font
*Italics font* Italics font
~~Strikethrough~~ Strikethrough
19^th^ 19th
H~2~O H2O
++Inserted text++ Inserted text
==Marked text== Marked text
[link text](https:// "title") Link
![image alt](https:// "title") Image
`Code` Code 在筆記中貼入程式碼
```javascript
var i = 0;
```
var i = 0;
:smile: :smile: Emoji list
{%youtube youtube_id %} Externals
$L^aT_eX$ LaTeX
:::info
This is a alert area.
:::

This is a alert area.

Versions and GitHub Sync
Get Full History Access

  • Edit version name
  • Delete

revision author avatar     named on  

More Less

Note content is identical to the latest version.
Compare
    Choose a version
    No search result
    Version not found
Sign in to link this note to GitHub
Learn more
This note is not linked with GitHub
 

Feedback

Submission failed, please try again

Thanks for your support.

On a scale of 0-10, how likely is it that you would recommend HackMD to your friends, family or business associates?

Please give us some advice and help us improve HackMD.

 

Thanks for your feedback

Remove version name

Do you want to remove this version name and description?

Transfer ownership

Transfer to
    Warning: is a public team. If you transfer note to this team, everyone on the web can find and read this note.

      Link with GitHub

      Please authorize HackMD on GitHub
      • Please sign in to GitHub and install the HackMD app on your GitHub repo.
      • HackMD links with GitHub through a GitHub App. You can choose which repo to install our App.
      Learn more  Sign in to GitHub

      Push the note to GitHub Push to GitHub Pull a file from GitHub

        Authorize again
       

      Choose which file to push to

      Select repo
      Refresh Authorize more repos
      Select branch
      Select file
      Select branch
      Choose version(s) to push
      • Save a new version and push
      • Choose from existing versions
      Include title and tags
      Available push count

      Pull from GitHub

       
      File from GitHub
      File from HackMD

      GitHub Link Settings

      File linked

      Linked by
      File path
      Last synced branch
      Available push count

      Danger Zone

      Unlink
      You will no longer receive notification when GitHub file changes after unlink.

      Syncing

      Push failed

      Push successfully