Skip to content

Tracking Issue for RFC 3137: let-else statements #87335

Closed

Description

@joshtriplett

This is a tracking issue for RFC 3137: let-else statements.
The feature gate for the issue is #![feature(let_else)].

About tracking issues

Tracking issues are used to record the overall progress of implementation.
They are also used as hubs connecting to other relevant issues, e.g., bugs or open design questions.
A tracking issue is however not meant for large scale discussion, questions, or bug reports about a feature.
Instead, open a dedicated issue for the specific matter and add the relevant feature gate label.

Steps

Unresolved Questions

  • Readability in practice: Will let ... else { ... }; be clear enough to humans in practical code, or will some introducer syntax be desirable?
  • Conflicts with if-let-chains: Does this conflict too much with the if-let-chains RFC or vice-versa? Neither this feature nor that feature should be stabilized without considering the other.
  • Amount of special cases: Are there too many special-case interactions with other features?
  • Grammar clarity: Does the grammar need to be clarified? This RFC has some slightly unusual grammar requirements.

Implementation history

Activity

  1. added
    T-langRelevant to the language team
    C-tracking-issueCategory: An issue tracking the progress of sth. like the implementation of an RFC
    F-let_elseIssues related to let-else statements (RFC 3137)
    on Jul 21, 2021
  2. inquisitivecrystal commented on Jul 21, 2021

    @inquisitivecrystal
  3. LeSeulArtichaut commented on Jul 21, 2021

    @LeSeulArtichaut
    Contributor

    Is there already someone who wants to implement the RFC? Otherwise I could maybe take a stab at it

  4. camsteffen commented on Jul 21, 2021

    @camsteffen
    Contributor

    I volunteered on Zulip to implement a while ago and I have a start on it. @Fishrock123 are you still okay with me taking it since you were also interested?

  5. Fishrock123 commented on Jul 21, 2021

    @Fishrock123
    Contributor

    I'm interested but I'm clueless and pretty busy. I'd say you should just take it.

  6. matklad commented on Aug 18, 2021

    @matklad
    Contributor

    An interesting example which I didn't see come up in the RFC text/thread (quick search for mut). In today's Rust, let-else won't work for a case of updating variables. An example here is computing a path in a graph after DFS returns a map of parents:

    let parents: HashMap<u32, u32> = dfs();
    let mut curr: u32 = 0;
    let mut path: Vec<u32> = Vec::new();
    loop {
        path.push(curr);
        let Some(&curr) = parents.get(curr) else { break }
    }

    Here, let won't re-bind curr, it'll shadow it instead.

    Just as an observation, Kotlin-style elvis operator would avoid this issue, as it doesn't require a pattern:

    let parents: HashMap<u32, u32> = dfs();
    let mut curr: u32 = 0;
    let mut path: Vec<u32> = Vec::new();
    loop {
        path.push(curr);
        curr = *(parents.get(curr) ?: break);
    }
  7. joshtriplett commented on Aug 18, 2021

    @joshtriplett
    Author
  8. matklad commented on Aug 18, 2021

    @matklad
  9. matklad commented on Aug 18, 2021

    @matklad
    Contributor

    Hypothetically, we can allow the following (else without let):

    let parents: HashMap<u32, u32> = dfs();
    let mut curr: u32 = 0;
    let mut path: Vec<u32> = Vec::new();
    loop {
        path.push(curr);
        Some(&curr) = parents.get(curr) else { break }
    }

    This would be a natural combination of let-else and #71126. While #71126 is still unstable, it might be worth is to think about the interactions between the two. Will the above example just work, or are there any problems with it?

  10. camsteffen commented on Aug 18, 2021

    @camsteffen
    Contributor

    I also mentioned this in the RFC. Probably no technical limitation but just readability concerns. I think we can safely wait until let-else and destructuring-assignment have matured before expecting them to procreate (forgive me).

  11. Fishrock123 commented on Aug 18, 2021

    @Fishrock123
    Contributor

    Ah, I seem to have missed that thing about destructuring_assignment, yes that seems like a future possibility, with the above mentioned caveats.

  12. added a commit that references this issue on Sep 1, 2021
  13. Fishrock123 commented on Sep 1, 2021

    @Fishrock123
    Contributor

    @joshtriplett #87688 has been merged, can you tick off "implement the RFC"?

  14. 77 remaining items

  15. Fishrock123 commented on Jul 23, 2022

    @Fishrock123
    Contributor

    @wmstack Please see the “Future Possibilities: Fallback Assignment” section of the RFC.

    The RFC leaves this open as a possible addition by using some additional syntax. See the “else-block fall-back assignment” rationale section for why.

  16. kevincox commented on Jul 23, 2022

    @kevincox
    Contributor

    Strong agree. Let's start with the simple case and we can consider options like that in a new RFC with some real-world experience under our belt.

  17. est31 commented on Jul 28, 2022

    @est31
    Member

    In theory there are some scenarios where we could drop earlier. E.g. in this one:

    #![feature(let_else)]
    
    struct Droppy(u32);
    
    impl Drop for Droppy {
        fn drop(&mut self) {
            println!("I dropped");
        }
    }
    
    #[allow(irrefutable_let_patterns)]
    fn main() {
        {
            let &Droppy(v) = &Droppy(32) else { 
                todo!()
            };
            println!("v is {v}");
            println!("Drop will happen soon");
        }
        println!("--- Next ---");
        {
            let &Droppy(_) = &Droppy(32) else { 
                todo!()
            };
            println!("I should have dropped");
        }
    }

    It prints:

    v is 32
    Drop will happen soon
    I dropped
    --- Next ---
    I should have dropped
    I dropped
    

    The first example makes sense: v is not moved out of Droppy because we only have it by reference. In the second example, however, no binding is created, we only do pattern matching on Droppy. Basically we could replace it with a use of matches!().

    Note that this is similar however to what we do with irrefutable patterns already. They drop immediately when it's a by-val pattern match but live on if it's a by-ref pattern match, even if no bindings are being created.

        {
            let Droppy(_) = Droppy(32);
            println!("After the statement");
        }
        println!("--- Next ---");
        {
            let &Droppy(_) = &Droppy(32);
            println!("After the statement");
        }

    Prints:

    I dropped
    After the statement
    --- Next ---
    After the statement
    I dropped
    

    Should I open a new issue for discussion?

  18. est31 commented on Aug 30, 2022

    @est31
    Member

    In theory there are some scenarios where we could drop earlier.

    Answering myself: I think this comment by @joshtriplett resolves this: We should just do whatever let is doing. As the current behaviour matches let, I think we can stabilize it this way (although tests for it would be good, reminder that I add them to #99291).

  19. added a commit that references this issue on Sep 5, 2022
  20. added 2 commits that reference this issue on Sep 5, 2022
  21. oxalica commented on Sep 7, 2022

    @oxalica
    Contributor

    The current proposal-ed syntax accepts no pattern in the else branch. This makes it unhelpful with another common pattern,

    let val = match ... {
        Ok(val) => val,
        Err(e) => { /*use e here!*/ return; } // Or `panic`, `continue`, whatever.
    };

    We may workaround by let Ok(val) = (...).map_err(|e| /* use e */) else { return; } but it is 1. ugly, 2. fail to compile if both Ok and Err branches take ownership of some variables.

    Note that Idris has a similar syntax which supports "else-arms" in case of failed let binding.

    do
      Right x <- readNumber | Left y => ... -- <- Interrupts the normal control flow but can have own bindings.
      -- More actions...

    This reminds me that general let-else should be a special match with one arm binding to current scope.
    Could we do better in this case? Syntax like let Ok(e) = ... else match { Err(e) => { ... } } could work but seems a bit too complicated.

  22. Eugeny commented on Sep 7, 2022

    @Eugeny

    IMO at that point it's easier to just let x = match ... with a deviating Err branch

  23. added a commit that references this issue on Sep 16, 2022
    d5fadc4
  24. added a commit that references this issue on Sep 17, 2022
    3ad81e0
  25. added a commit that references this issue on Mar 24, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

C-tracking-issueCategory: An issue tracking the progress of sth. like the implementation of an RFCF-let_elseIssues related to let-else statements (RFC 3137)S-tracking-impl-incompleteStatus: The implementation is incomplete.T-langRelevant to the language team

Type

No type

Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions