One case of making illegal states unrepresentable

(I am not sure if this is more fit for the Internals forum).

I've tried to use the Rust type system to "make illegal states unrepresentable" as some functional programmers say. The problem is very simple, there's a discrete machine with a simple state (three booleans) with only few legal states and I can only cycle between them. It's a traffic light. After a while and many alternative solutuions I've created this one:

#![allow(dead_code)]

mod traffic_light {
    use std::fmt;

    #[derive(PartialEq, Eq)]
    pub struct State { red: bool, green: bool, yellow: bool }

    pub const RED:    State = State { red: true,  green: false, yellow: false };
    pub const GREEN:  State = State { red: false, green: true,  yellow: false };
    pub const YELLOW: State = State { red: false, green: false, yellow: true  };

    impl State {
        pub fn next_state(&self) -> Self {
            match *self {
                RED    => GREEN,
                GREEN  => YELLOW,
                YELLOW => RED,
                _ => panic!("Invalid traffic_light State"),
            }
        }

        pub fn is_red_on(&self) -> bool    { self.red }
        pub fn is_green_on(&self) -> bool  { self.green }
        pub fn is_yellow_on(&self) -> bool { self.yellow }
    }

    impl fmt::Debug for State {
        fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
            match *self {
                RED    => write!(f, "Red traffic light"),
                GREEN  => write!(f, "Green traffic light"),
                YELLOW => write!(f, "Yellow traffic light"),
                _ => panic!("Invalid traffic_light State"),
            }
        }
    }
}

fn main() {
    let mut light = traffic_light::RED;
    println!("{:?}", light);

    for _ in 0 .. 5 {
        light = light.next_state();
        println!("{:?}", light);
    }
}

But that code isn't DRY and those panic aren't nice.

To remove the panics I've bundled the state inside variants of an enum:

#[derive(Debug)]
#[repr(u32)]
enum TrafficLight {
    Red    = 0b_1_0_0,
    Green  = 0b_0_1_0,
    Yellow = 0b_0_0_1,
}

This is efficient but not general enough...

So I think I'd like to write something like this:

#[derive(Debug)]
enum TrafficLight {
    Red { red: bool = true, green: bool = false, yellow: bool = false },
    Green { red: bool = false, green: bool = true, yellow: bool = false },
    Yellow { red: bool = false, green: bool = false, yellow: bool = true },
}

impl TrafficLight {
    fn next_state(&self) -> TrafficLight {
        use TrafficLight::*;

        match *self {
            Red    => Green,
            Green  => Yellow,
            Yellow => Red,
        }
    }
}

fn main() {
    let mut light = traffic_light::Red;
    println!("{:?} {}", light, light.red);

    for _ in 0 .. 5 {
        light = light.next_state();
        println!("{:?}", light);
    }
}

This requires no impl Debug, it contains no panics, and it doesn't need functions like is_red_on().

There was a proposal for "Default values for struct fields" that could allow this, but it's frozen or dead:

https://github.com/rust-lang/rfcs/issues/1594

I also think two standard libray functions (next_variant() and next_variant_wrapping()) should be avilable to advance to the next variant of an enum (as long as enum variants are simple or with fully filled data):

fn next_state(&self) -> TrafficLight {
    next_variant_wrapping(self)
}

Edit: you can do it in TypeScript, apparently (from Leverage union types in Typescript to avoid invalid states ):

type TrafficLightState =
    { red: true; yellow: false; green: false; } |
    { red: false; yellow: true; green: false; } |
    { red: false; yellow: false; green: true; }

function nextTrafficLightState(
    trafficLight: TrafficLightState): TrafficLightState {

    if (trafficLight.red) {
        return { red: false, yellow: true, green: false };
    } else if (trafficLight.yellow) {
        return { red: false, yellow: false, green: true };
    } else if (trafficLight.green) {
        return { red: true, yellow: false, green: false };
    }
}

Here TrafficLightState is only allowed to have those three values.

1 Like

Why do you need the red/yellow/green bool fields at all here? Is it just for illustration purposes?

1 Like

I've written lot of different solutions for this little problem, you don't need the boolean values inside an enum, you can use just an enum that represents the various states, plus a function that maps enum variants to a corresponding struct with the fields of your state (but one disadvantage of this is that you have both an enum and a struct with different meaning and different names). The boolean values in this specific problem denote if every specific lamp of a traffic light is switched on in a particular moment, you can think of it like the state details associated with each state of your machine.

Right. You can also represent the state as functions in this particular case. In fact, you'd probably get better codegen since the constants would be visible to the compiler rather than being "lost" in the fields.

Also, seems like an enum set in this case would convey the semantics nicely as well (i.e. which lights are on) without having to duplicate the state in a side struct.

This is all relative to this specific case. My post is asking if it's a good idea to add to Rust a syntax like this, where fields of the variants are filled:

enum TrafficLight {
    Red { red: bool = true, green: bool = false, yellow: bool = false },
    Green { red: bool = false, green: bool = true, yellow: bool = false },
    Yellow { red: bool = false, green: bool = false, yellow: bool = true },
}

I assume you'd want those field values to be "frozen"/constant - i.e. caller cannot do TrafficLight::Red {green: true, ...} type of thing, which is how enum struct variants work today. I suppose you'd perhaps also want a mix of frozen/const fields and user settable ones, and then it gets messy :slight_smile:

I can't think of a compelling usecase for this though. Maybe once associated consts makes its way into the language and if enum variants become their own types, you could mix those two things to similar effect.

I suppose you'd perhaps also want a mix of frozen/const fields and user settable ones, and then it gets messy

Enums are meant to be constant.

I can't think of a compelling usecase for this though.

To switch with compiler-safety and in a DRY way between states that contain more state than an u32 (or u128). I've tried to explain it in my whole first post, but I guess I've failed...

That's enums in other languages, where they really are named (possibly type safe) constants. In Rust, they're sum types, which can be used as constants but can also be used (and arguably, this is the more common use of them in Rust) as a closed set of structured types.

2 Likes

Try this:

enum TrafficLightState{
    Red,
    Yellow,
    Green
}

impl TrafficLightState{
    fn get_next(self) -> Self{
        match self{
            TrafficLightState::Red => TrafficLightState::Green,
            TrafficLightState::Yellow => TrafficLightState::Red,
            TrafficLightState::Green => TrafficLightState::Yellow,
        }
    }
}


fn main() {//idiomatic rust way to test for 'is_red'
    let state = TrafficLightState::Red;
    
    if let TrafficLightState::Red = state{
        //do stuff
    }
    
    //or use match
}

Coming from a bit of a Java background, I found Rust's notion of an 'enum' really confusing at first, because I assumed that the same keyword meant same semantics. As @vitalyd just said, Rust enums (when they contain data within {}) are more like tagged unions than C or Java style enums.

eg: in Java you might write

public enum TrafficLight {
    RED(true, false, false),
    GREEN(false, true, false),
    YELLOW(false, false, true)

    // class constructor etc.
}

Which creates 3 immutable, constant instantiations of the 'class' TrafficLight. Thus the data contained within is constant etc...

As people have explained, Rust enums are very different (closer from functional programming languages like Haskell than C or Java):

  • the 'tag' should be the one canonical source of what 'state' your enum is in (Red/Green/Yellow)
  • and the extra data is, well, extra - each instance can have completely different stuff there - it's not just extra data that corresponds one-to-one with the tag.

So therefore if you do want to use Rust enums, but also get some sort of bool state from it (although its surely more idiomatic to match directly as @burns47 has shown), write some functions like this:

fn is_green(s: TrafficLightState) -> bool {
    match s {
        TrafficLightState::Green => true,
        _ => false
    }
}
// and so on
2 Likes

My example was not very good, your're right, think of certain european traffic lights where instead of yellow you have both green and yellow switched on.

Hmm perhaps you can use this then?

https://github.com/contain-rs/enum-set

Edit: I have never used this so I'm not exactly sure if it fits the requirements..

@leonardo Wait do you want your code to be able to handle both types of traffic lights? Or just the European version?

Handling one traffic light is enough, but the point of this post is not to play with traffic lights, but to ask if it's a good idea to extend Rust to allow constant state associated with enum variants, this should simplify code in some cases, avoid using a mod{}, avoids a panic() OR it avoids mapping functions from a simple enum to constants. In future I'll try to write my posts in a different way, to express my purposes more clearly.

I only asked since I was wondering why the European scenario doesn't boil down to

enum TrafficLightState{
    Red,
    YellowAndGreen,
    Green
}

I think I'm missing conceptually what you're trying to do. IMO, any function that can be written like you're trying to do:

impl TrafficLightState{
    fn do_something(&self){
          if self.get_state().is_green{ //get_state ambiguous
           ...
          }
    }
}

Can be written equivalently as:

impl TrafficLightState{
    fn do_something(&self){
          match *self{
              TrafficLightState::Green {...}
              _ =>{}
          }
    }
}

I may need a more concrete use case

We have both "red and yellow" and "yellow only", depending on whether the light is transitioning towards green or red.

https://upload.wikimedia.org/wikipedia/commons/2/24/Traffic_lights_4_states.svg

But then, I would represent European / non-Europear trafic lights with two different enum types which both implement a TraficLight trait, defining .red_on(), .yellow_on() and .green_on().

IMHO, the point of "making impossible states impossible" is to use our advanced enum system, instead of assertions about the value of variables.

One more version, this is more or less equivalent to the version that Rust doesn't allow you to write (but with more boilerplate ans less DRY):

mod traffic_light {
    #[derive(Debug)]
    pub enum TrafficLight { Red, Green, Yellow }

    struct LightsState { red: bool, green: bool, yellow: bool }

    impl TrafficLight {
        pub fn next(&self) -> Self {
            use self::TrafficLight::*;

            match *self {
                Red    => Green,
                Green  => Yellow,
                Yellow => Red,
            }
        }

        fn get_state(&self) -> LightsState {
            use self::TrafficLight::*;

            match *self {
                Red    => LightsState { red: true,  green: false, yellow: false },
                Green  => LightsState { red: false, green: true,  yellow: false },
                Yellow => LightsState { red: false, green: false, yellow: true  },
            }
        }

        pub fn get_red(&self) -> bool    { self.get_state().red }
        pub fn get_green(&self) -> bool  { self.get_state().green }
        pub fn get_yellow(&self) -> bool { self.get_state().yellow }
    }
}

fn main() {
    let mut light = traffic_light::TrafficLight::Red;
    println!("{:?}  red: {}, green: {}, yellow: {}",
             light, light.get_red(), light.get_green(), light.get_yellow());

    for _ in 0 .. 5 {
        light = light.next();
        println!("{:?}  red: {}, green: {}, yellow: {}",
                 light, light.get_red(), light.get_green(), light.get_yellow());
    }
}