Examples of modeling a domain with strong type safety, expressiveness and performance


#1

I’m pretty impressed with how diesel uses types to model its domain, allowing for both expressiveness, type safety, and zero-cost abstractions. It’s probably one of the most trait-heavy crates that I’ve seen (one can argue the pros/cons of this aspect alone, but we can save that for another thread :slight_smile:) . It’s also a good example of leveraging Rust’s type system to model a domain safely, fairly concisely/ergonomically (from user’s standpoint), and not pay an abstraction cost at runtime.

Are there any other crates that people feel achieve these aspects well? I’m aware of a few other crates that do interesting things with the type system, such as typenum, hlist/frunk, nalgebra, dimensioned, and perhaps a few similar ones that I’m forgetting.

Another way to frame my question is: what crate(s) would you show to, say, a coworker (that’s familiar with Rust’s “out of the box” features) that demonstrates a tastefully done assembly of Rust’s (more advanced) features to implement a perfomant and type safe system? The idea is the coworker likes the individual tools in Rust’s toolbox, but would like to see what a good craftsman has been able to build with them - what codebase(s) would you show them?


#2

Personally, I’ve been quite impressed by rayon in this department. The way it manages to leverage standard Rust features in order to transparently integrate data parallelism within the existing container and iterator ecosystem is quite awe-inducing.

It is also a pretty nice example of a well-done layered architectural design, where if you are unhappy with the top-level abstraction you can easily reach the lower-level building blocks below and build yourself a better home with them.

EDIT: Serde is another good examples of cool compile-time Rust that I would gladly show to a coworker. For the most part, it Just Works with most popular file formats, and I have heared nothing but praise from those who have tried to extend it in order to support new serialization and deserialization schemes.

Projects which make me very curious, but which I haven’t tried/explored enough to make a definite statement about yet, include relm (a pretty elegant, though heavily WIP, GUI framework mapping Elm’s reactive design to GTK constructs) and @tomaka’s glium and vulkano safe wrappers around Khronos graphics APIs.

And then there are projects like nom and futures, which I would be more hesitant to show others at this point in time because I think they need some documentation/tutorial/ergonomics improvements, but whose implementation are nevertheless a pretty impressive example of what today’s Rust can do at compile time.


#3

In my own projects I usually have a Thing and UnvalidatedThing types for anything that requires validation. It’s a simple little thing that saves me a LOT of headache.


#4

Have you experimented with a session type style? ie Thing<Raw> and a Thing<Validated>


#5

I haven’t used session type style yet. I will try it in future projects though. :slight_smile:


#6

I finally found what was the other cool example of compile-time Rust which I had in mind back when you wrote this topic, but couldn’t remember the name at the time: structopt.

I have not yet needed to parse complex command line arguments in Rust, but if I did, the custom derive based interface of this library sounds like one of the coolest ways to go at it. It allows you to spread the description of your CLI interface across the arguments (instead of having one single huge builder method chain), and it sounds like it could save one from the tyrany of hashmaps with optional entries and associated unwraps/error handling.

Unlike docopt, which is another example of very cool CLI library design, structopt also marks a clear distinction between the interface that you expose to your users, and the interface that you use internally, which I personally feel to be important.


#7

Structopt is definitely in my list of top 5 favourite crates, besides serde, failure, slog, and rayon. A lot of the stuff I do will involve creating a small CLI program to wrap a process or library functionality so it can be conveniently used from the terminal. Now I’ve started using structopt I really don’t want to go back.

Another example of where traits and strong type-safety really helps with expressiveness and performance is the entire custom derives mechanism. For work I was experimenting with writing a Rust program to talk with a machine. The current (legacy :cry:) program has a 1000-odd line switch statement for dealing with the different types of messages which may be sent (then another 500-1000 for received messages). In less than a hundred lines of Rust I created a Request and Response trait, then a custom derive which serialises each field of the message.

It may sound underwhelming, but being able to swap out a couple thousand lines of hand-written, organically-evolved spaghetti code with two traits and a couple dozen trivial struct definitions was an incredibly empowering experience! Serialising a message is essentially just a case of writing each field to an io::Writer, so more traditional techniques like reflection (in go or java) would have added unacceptable amounts of overhead.


#8

structopt is a very nice crate indeed - I’ve used it a couple of times, admittedly for a fairly trivial CLI. The custom derive/proc macro facility itself, as a whole, is very slick - there’re definitely lots of cool things you can build with it (and people already have).

I’ll admit that I wasn’t thinking of this particular feature when starting this thread. I had in mind approaches like session types, clever (for some definition of clever) uses of generics, things like lifetime-based value signing, and so on.

But, thanks for mentioning custom derives (and @Michael-F-Bryan’s example as well).


#9

Have you read about Japaric’s mechanism for managing concurrent hardware access in an embedded environment? He uses session types and similar type-level trickery to statically determine who gets access to hardware and which tasks can be pre-empted by other tasks.

His blog talks about it in a lot more detail:


#10

I’ve seen some of @japaric’s blog posts before - they’re great! I don’t recall seeing any particular type system tricks in those but it’s possible I’m misremembering or simply didn’t see the posts with them. I’ll take a look again. Thanks