A minor but interesting and illuminating point came up in a conversation that I thought was worth sharing, for those learning rust, in a separate post. I’m mostly just copy-and-pasting here.


TL;DR: The patterns you use in match statements and related syntax are (basically) available to you in let statements. This is how destructuring works!! And what they’re for with let. But the patterns have to be “irrefutable” — IE, they have to always be able to match the expression/value.


For those who aren’t aware, here’s the first section in The Book on patterns in let statements.

I think, if this is confusing, there are two points of clarification:

  1. All let statements involve patterns (as Ephera states). They’re all let PATTERN = EXPRESSION.
    • For ordinary variable binding, we’re just providing a basic pattern that is essentially like a wildcard in that it will match the totality of any expression and so be bound to the full/final value of that expression.
    • It’s only when the pattern becomes more complex that the pattern matching part becomes evident, as elements of the value/expression are destructured into those of the pattern.
    • EG, let (x, y, _) = (1, 2, 3); or Ephera’s example below let Something(different_thing) = something; which extracts the single field of the struct something into the variable different_thing (handy!).
  2. let statements must use irrefutable patterns. That is, patterns that cannot fail to match the expression.
    • For example, against a tuple, (x, y, _) will always match. Another way of putting it: irrefutable patterns are about destructuring not testing or conditional logic.
    • if let statements on the other hand can take both irrefutable patterns and refutable, but are really intended to be used with refutable patterns as they’re intended for conditional logic where the pattern must be able to fail to match the expression/value.
    • See The Book chapter on refutability

The nice and useful example provided by @Ephera@lemmy.ml (in the original comment):

struct Something(DifferentThing);

let something = Something(DifferentThing::new());

let Something(different_thing) = something;
  • Here, the variable something is a struct of type Something which contains one field of type DifferentThing.
  • In the final line, we’re extracting that DifferentThing field with a pattern in a let statement.
  • IE, Something(different_thing) is the pattern. And it is irrefutable against all variables of type Something, as they have only one field.
  • Sibbo@sopuli.xyz
    link
    fedilink
    English
    arrow-up
    8
    ·
    edit-2
    7 months ago

    This is outdated information. Nowadays, you can use let <pattern> = <expression> else <block>. This is called a let-else statement.

    The pattern does not need to be irrefutable, as if it does not match, the else block is executed. But the block must diverge, i.e. contain a return, continue or break statement to avoid any code in which bindings made in the let statement would be valid.

    • maegul (he/they)OPM
      link
      fedilink
      English
      arrow-up
      6
      ·
      7 months ago

      Thanks!!

      Some quick searching uncovered the following:

      Personally, skimming through that RFC left me pretty unconvinced. I think I’m going to ignore this and just assign/bind with match statements instead.

      The RFC provides this example for why the let-else is “better”:

      Using match:

      let features = match geojson {
          GeoJson::FeatureCollection(features) => features,
          _ => {
              return Err(format_err_status!(
                  422,
                  "GeoJSON was not a Feature Collection",
              ));
          }
      };
      

      Using let-else:

      let GeoJson::FeatureCollection(features) = geojson else {
          return Err(format_err_status!(
              422,
              "GeoJSON was not a Feature Collection",
          ));
      };
      

      … and yea, for me, nah. Readability counts and match statements provide a lovely and consistent structure that lets you know what its doing.

      The Drawbacks section in the RFC resonates with this. You gotta know to provide the else block if your pattern is refutable and you gotta be prepared to distinguish let-else statements and if-else expressions. EG, what’s let PATTERN = if { a } else { b } else { c };??

      Still cool though I guess.