Skip to content

Generic let #2297

Closed
Closed
@SoniEx2

Description

@SoniEx2

I'm tired of writing code like the following:

impl<'a> IrcCommand<'a> {
    pub fn new(cmd: &'a [u8]) -> IrcCommand<'a> {
        if cmd.len() == 3 && match (cmd[0], cmd[1], cmd[2]) {
            (b'0'...b'9', b'0'...b'9', b'0'...b'9') => {
                // TODO switch to TryFrom/TryInto once those are stable. (rust-lang/rust#33417)
                return IrcCommand::Numeric(Numeric::new(array_ref![cmd,0,3]));
            },
            _ => false
        } {
            unreachable!()
        } else {
            IrcCommand::Stringy(Stringy(cmd))
        }
    }
}

With generic let, I'd be able to write instead:

impl<'a> IrcCommand<'a> {
    pub fn new(cmd: &'a [u8]) -> IrcCommand<'a> {
        if cmd.len() == 3 && let (b'0'...b'9', b'0'...b'9', b'0'...b'9') = (cmd[0], cmd[1], cmd[2]) {
            // TODO switch to TryFrom/TryInto once those are stable. (rust-lang/rust#33417)
            IrcCommand::Numeric(Numeric::new(array_ref![cmd,0,3]))
        } else {
            IrcCommand::Stringy(Stringy(cmd))
        }
    }
}

Generic let works as follows:

let pattern = value becomes an expression, its return value is true if the pattern matches (always true for irrefutable patterns), false if it doesn't.

It binds the names when it matches, and only when it matches, so the compiler needs to check the true path for the names, and the false path for the lack of the names.

In the case of irrefutable patterns, the result is always true, so this is always valid:

let x = 1u8;
// this path is always true
println!("{}", x);

Using a refutable pattern in that let would cause the compiler to error.

Other cases where this is nice:

#2086 #935 etc.

while let data = do_some_io()? { // while we have lines
    println!("{}", data); // print the lines
}
if let Some(x @ '0'...'9' | x @ 'A'...'F' | x @ 'a'...'f') = v {
    println!("got hex: {}", x);
}
// OR
if (let Some(x @ '0'...'9') = v) || (let Some(x @ 'A'...'F') = v) || let Some(x @ 'a'...'f') = v { // note that "x" must appear in every || case.
    println!("got hex: {}", x);
}
// (with a strong preference for the former, ofc, as the latter needs parens)

Corner cases:

  • let would have the same precedence it does today. this means in patterns, you'd have to write if (let x = y) && ... (parens required except as the last expr). but it does give backwards compatibility with basically anything you can think of: if let true = p && q {, etc would still work.
  • I can't think of any other corner cases because every corner case I can think of is either an explicit consequence of this feature (e.g. irrefutable patterns in if-let and while-let, which still give a compiler warning as proposed in RFC: Allow Irrefutable Patterns in if-let and while-let statements #2086 as the compiler can guarantee it's always true and while true {} gives a warning), or it doesn't work and still won't work even with this feature.

Metadata

Metadata

Assignees

No one assigned

    Labels

    T-langRelevant to the language team, which will review and decide on the RFC.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions