DEV Community

Cover image for Making impossible things impossible in Rust
stevensonmt
stevensonmt

Posted on

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.

Top comments (4)

Collapse
 
eljayadobe profile image
Eljay-Adobe

I'm impressed with the challenge of taking core language competencies of Elm, and trying to apply them to Rust.

Rust's core language competencies is very different from Elm's core language competencies.

What Rust is really good at -- a super-strong ownership model that is enforced at compile-time and eliminates a huge category of programming errors -- Elm does not need (because it only deals with immutable structures and uses garbage collection).

Collapse
 
stevensonmt profile image
stevensonmt

I have found my attempts at learning Elm have been really helpful in my ongoing attempt to learn Rust and programming in general. I don't know if that is inherent to the language design or the quality of documentation/tutorials. I picked up Rust for about three months, then focused on more front end programming for a while, which is when I tried my hand at Elm. Coming back to Rust after that many concepts just made sense in a way they had not the first time around.

Collapse
 
eljayadobe profile image
Eljay-Adobe

Other languages that have the same kind of goodness that Elm has: Haskell, F#, OCaml.

I've heard Reason also should be in that category, but I'm not familiar with that language.

F# is like "OCaml for .NET". It's a first-class language fully supported by Microsoft in the .NET space, including Mono and the formerly-known-as Xamarin.

A little further afield, but may also be of interest: Scala, Elixir, Clojure (especially if you like Lisp/Scheme).

I have not programmed much in Rust. It does appeal to my OCD.

Thread Thread
 
stevensonmt profile image
stevensonmt

I actually read through the elixir docs while bedridden a few months ago. Decided to dive back into rust instead of switching to another language.