Making impossible things impossible in Rust

stevensonmt

stevensonmt

Posted on June 6, 2018

Making impossible things impossible in Rust

I watched Richard Feldman's talk Making Impossible States Impossible a while back when I was learning a little bit of Elm. I'm not sure I understood the execution described in his talk, but the concept of making errors literally impossible with correct data structures stuck with me. I'm in the middle of what is my most ambitious Rust project to date and struggling to get that proper data structure.

The problem centers around needing to create search strings for a web API that applies to 30+ database sets, each with unique but overlapping available search fields up to the 70s. I need it to be impossible to select an inappropriate field for a given database.

In the genericized code below I'd like to share a couple of approaches that have become clear to me as I go through this design process. In doing so I hope to improve my own understanding and hopefully that of others with regards to type safety, implementation of traits, and data structures in Rust.

enum Foo {
    Foo1(Foo1Field),
    Foo2(Foo2Field),
}

enum Foo1Field {
    Foo1Field1,
    Foo1Field2,
}

enum Foo2Field {
    Foo2Field1,
    Foo2Field2,
}

struct Bar {
    foo: Foo
}

Enter fullscreen mode Exit fullscreen mode

With this organization you know that every Bar instance will always have a "field" type appropriate to the variant of Foo assigned. It's a little cumbersome to try and access those "fields" though. This is in part because the tuples of Foo1 and Foo2 are of different types.

An alternative is making more use of traits.

enum Foo1Field {
    Foo1Field1,
    Foo1Field2,
}

enum Foo2Field {
    Foo2Field1,
    Foo2Field2,
}

trait Foo {}

impl Foo for Foo1Field {}
impl Foo for Foo2Field {}

struct Bar<T: Foo> {
    foo: T
}

Enter fullscreen mode Exit fullscreen mode

With this structure I no longer need a Foo enum, I just need the "field" enums to implement the Foo trait. The Bar struct now takes any enum that implements the Foo trait for its foo field. Accessing the field values is now trivial.

let bar = Bar { foo: Foo1Field::Foo1Field1 };
bar.foo // returns Foo1Field1 //
let bar2 = Bar { foo: Foo2Field::Foo2Field2 };
bar2.foo // returns Foo2Field2

Enter fullscreen mode Exit fullscreen mode

This also allows me to add fields to various database sets without duplicating fields. I simply implement a trait for the new set. Hopefully others will find this helpful. I look forward to hearing additional insights or suggestions for alternative approaches to this type of problem. Thanks for reading.

💖 💪 🙅 🚩
stevensonmt
stevensonmt

Posted on June 6, 2018

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related

December Surely Looks Busy!
opensource December Surely Looks Busy!

November 29, 2024

December Surely Looks Busy!
opensource December Surely Looks Busy!

November 29, 2024

Daemons on macOS with Rust
undefined Daemons on macOS with Rust

November 29, 2024