Hacker Newsnew | past | comments | ask | show | jobs | submitlogin
A Fistful of States: More State Machine Patterns in Rust (deislabs.io)
99 points by lukastyrychtr on Oct 2, 2020 | hide | past | favorite | 25 comments


A request about the page, rather than the content: please remove the preloader (element with ID preload), all it does is slow down page load and cause problems if the relevant JavaScript doesn’t load (whether by deliberate user choice or just because things don’t always work smoothly). There used to be reasons, however dubious, for such loading indicators, but those reasons are all roughly obsolete now.


Who is writing state machines without the pattern of generators/coroutines these days?


Who is writing state machines without the pattern of generators/coroutines these days?

I prefer the discipline and diagrams of regular state machines. Personally, I don't like the coroutine alternative because it takes away what is good and adds complexity.

Also as @R0b0t1 says, regular state machines are used in embedded:

- Draw a diagram

- Implement a state machine

These feels much more like engineering that messing with coroutines.


Embedded.


You can use async/await (and therefore generators) in Rust on embedded just fine.


Most people are not doing embedded with Rust.


So why did you bring it up?


GP wasn't about Rust, it's about people using state machines without coroutines.


Ah i see I had misread your comment as “most people aren’t using rust for embedded” rather than “most people doing embedded aren’t using rust”, sorry about that.


One of my favorite things about Swift is its rich enums that (similar to Rust) serve as the building blocks for state machines. Swift has the advantage of expressive switch statements with pattern matching that have allowed me to simplify and condense the "which state should I go to next given x" type of logic.

I'm not trying to say Swift is better, by the way.


How is Swift's switch different to Rust's match?


Mostly match recurrence. One pain point about Rust is, due to its linear nature, you cannot use `as` patterns, such as:

match e with x as (y, z) -> (x[0], x[1]) == (y, z)

This is because the matcher itself must take ownership of the expression, and destructuring is an ownership transfer.


Can you point me to the swift docs for that? I don't understand what you are trying to do.

You can bind references to what you are matching with @name. You also don't need to take ownership of a value. You can match on a reference to it with &.

However without understanding your example it is hard to say for sure.


> You can bind references to what you are matching with @name.

Given what they're saying I assume they're trying to match both the entire value and the sub-values e.g.

    xs@(_:xs')
in Haskell. Stable Rust currently lets you use at-patterns and perform assertions on the sub-pattern, but not match there.

> You also don't need to take ownership of a value. You can match on a reference to it with &.

`ref` is what you'd use in a pattern though, `&` would deref' a reference so usually it'd be to Copy a Copy type which is provided behind a ref'.


> match both the entire value and the sub-values

I see. Yes, it appears to be either-or for now.

> `ref` is what you'd use in a pattern though

Not in the pattern, in the "argument". Like this: https://rust.godbolt.org/z/eT366s

  pub fn f(v: &Option<String>) {
      match &v {
          other => eprintln!("Doesn't move {:?}", other),
      }
  }


You can do both. Your code is closer stylistically to what you'd want to use most of the time, but every now and then I encounter a case where using ref in a single pattern makes more sense.


For sure sure. My point is that you don't have to move out.


> This is because the matcher itself must take ownership of the expression, and destructuring is an ownership transfer.

It doesn't, you can bind by references. It's more fiddly but nothing precludes the combination of at-patterns and sub-pattern matchings, though it's not stable yet there's a tracking issue for at-and-sub-patterns: https://github.com/rust-lang/rust/issues/65490 and it works on nightly: https://play.rust-lang.org/?version=nightly&mode=debug&editi...

There's also an issue for patterns combining by-ref and by-move bindings: https://github.com/rust-lang/rust/issues/68354

Also… does Swift even have as-patterns?


I would personally rely on match ergonomics[1] and borrow e instead of using ref in the pattern[2].

[1]: https://github.com/rust-lang/rfcs/blob/master/text/2005-matc...

[2]: https://play.rust-lang.org/?version=nightly&mode=debug&editi...


Match ergonomics is one thing I really dislike due to how much explicitness it trades for terseness.


The only thing that that is bugging me at this very moment is that Swift's switch is a statement and not an expression. Ugh!


I'm not an expert in Rust, so some of these things might be available in it's match (just in a way I'm unfamiliar with), but for me the big advantages of Swift's switch are:

- switching on tuples (not wrapped in a var): this prevents doubly-nested switches, which I've always found to be a problem in control logic. - unwrapping optionals - matching against an enum's associated value, and unwrapping it - using `where` clauses as part of the pattern - matching against type casts


Rust has all that functionality, but as the author mentioned, they avoided using enums and match statements because it led to more runtime errors and they were specifically trying to incur compile time errors. This is why they choose to use trait objects and type definitions to control state transitions.

Edit: I invite you to learn more about the match keyword in rust and see how it relates to swift. I think you’ll find that it meets or exceeds your expectations: https://doc.rust-lang.org/book/ch06-02-match.html


Aside from the last, this is all very standard stuff, I don't mean for Rust I mean for language with sum types in general.

> - switching on tuples (not wrapped in a var): this prevents doubly-nested switches, which I've always found to be a problem in control logic

    match t {
        (Some(a), Some(b)) => a + b,
        (Some(a), None) => a,
        (None, Some(b)) => b,
        (None, None) => 0
    }
                      
> - unwrapping optionals

    match opt {
        Some(v) => v,
        None => 0
    }
although it's common to use HOFs, the `?` operator, or `if let`.

> - matching against an enum's associated value, and unwrapping it

See above.

- using `where` clauses as part of the pattern

    match opt {
        Some(v) if v > 5 => v - 5,
        Some(v) => v,
        None => 0
    }
> - matching against type casts

Rust doesn't really do subtyping, so that's not a case which would come up. I guess the closest would be matching on the result of a downcast_ref (https://doc.rust-lang.org/std/any/trait.Any.html#method.down...) but that just returns an Option so there's nothing special to it.


I don't think OP was necessarily claiming they were different




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: