Which Design Patterns are relevant to Rust

The documentation says:

some definitions would classify Rust as object oriented, but other definitions would not. In this chapter, we’ll explore certain characteristics that are commonly considered object oriented and how those characteristics translate to idiomatic Rust. We’ll then show you how to implement an object-oriented design pattern in Rust and discuss the trade-offs of doing so versus implementing a solution using some of Rust’s strengths instead.

I am going through:

  • Head First Design Patterns
  • Design Patterns: Elements of Reusable Object-Oriented Software

I am not understanding whether as a rust developer, reading these books will be useful or not.

I guess programming language eliminate the need of some design patterns. The problems these books address, how many of these are relevant to rust? To be more direct, which object-oriented Design Patterns I do not need to learn when using Rust and why?

Most likely, understanding design patterns in other languages is useful. Not in the sense that you will be able to re-use or translate code in a one-to-one fashion, but in a sense that you will have a feeling as to how to approach common code organization problems.

Some of the design patterns, specifically those not depending on object-orientation, do still apply to Rust. For example:

  • the Visitor pattern is a pretty universal way of dispatching callback methods on an external type, and it is in fact the way Serde deserializers are implemented.
  • the standard library utilizes the Private Implementation (PIMPL/Flyweight) pattern extensively, for hiding OS-specific details in otherwise cross-platform abstractions such as Path.
  • the builder pattern is used in many crates that offer large amounts of customizable configuration options.
  • C FFI wrappers can be considered as a form of the Facade pattern, because the public API of the C library is often full of uninteresting technical details by necessity (e.g. memory management). By wrapping the C API into a typed Rust API, one achieves a much simpler interface to program against.

But keep in mind that these are not hard rules! Especially considering your last question:

I would be wary of saying "you don't need to learn pattern X". I would equally caution against stating "you should learn pattern Y [and use it in Rust]". I never regarded patterns as something "to learn", like some sort of subject. Patterns usually arise naturally once you gain enough experience – naming them is useful for communicating with other programmers, but it is not the case that patterns must be applied exactly in some situations and not at all in others.

So I would say: go ahead with the books you are reading, learn something about every pattern because they might come in handy later, and write your own code however you think it's the most beautiful. Don't feel obliged to use patterns, but feel free to use them if they fit.

13 Likes

I found in the documentation that:

Rust is an imperative language, but it follows many functional programming paradigms.

Rust also seems to use Iterators and Closures.

I found an interesting blog post which says:

As you can see, it seems that some of the patterns that have evolved within the OOP world seem to give those languages the benefits that FP gets for free, without losing the things that characterize OOP like control over state and data.

So, I am assuming there are some Rust language features which help us bypass some design patterns which are necessary for OOP. What are these features? I am looking for something like "use a, b, c features to detour x, y, z design patterns". Is there any good resource that can answer this question?

Is there any design patterns that do not apply to OOP but in Rust (for example any design pattern that is not used for OOP but for FP)?

I don't think there is – or can be – such a direct equivalence.

Rust's language features are largely centered around correctness and memory safety. They are invented for managing memory correctly and efficiently, and for expressing sophisticated type-level constraints in order to model domain requirements as closely as possible. They are not invented for the purpose of building design patterns into the language.

I find it quite pointless to label languages this or that. Hardly any modern and popular language is purely OO or FP or imperative or anything. Most languages learn/borrow/steal from each other, and features end up being mixed and matched. Rust is no exception, although it comes with its unique set of features, which, however, aren't really about opinionated code organization, they are about memory safety. I have discussed this in Is Rust OOP? If not, what is it oriented to? - #6 by H2CO3.

I think experienced Rust programmers have invented a number of patterns that became commonplace in the ecosystem. Incidentally, there are several crates dedicated to the codification of such patterns, which alone is an interesting case study. From what I can tell, they are mostly about the application of types and Rust's strong type checking to domain constraints. Most of these patterns, I believe, lack a name, but here are a couple of ideas I've seen recurring in many code bases:

– Serde is notable not only for the visitor pattern, but for a reflection-free, purely static, macro- and trait-(interface-)based approach to being basically completely agnostic of the supported data formats and user-defined types. It's basically a sentient, benevolent, two-sided facade, if you wish.
– Clap (and formerly, StructOpt) brings parts of the same idea over to command-line argument parsing.
– Diesel's query builder basically embeds (a subset of) SQL into Rust via the type system. It applies the typestate pattern extensively. (This is one that's been around for long enough in other languages that it actually has a name.)
– Procedural macros are used for building all sorts of smaller or larger EDSLs. For instance, the Pest library builds a complete parser from an EBNF-like grammar description, much like tools like lex and yacc/bison, except more conveniently, without external code generation.
– That said, the "combinator library" approach (that is prevalent in languages traditionally called "functional") also exists in Rust. Nom is a parser combinator library, but even the standard library exhibits combinator-like features pertaining to monadic types (e.g. Option, Result, and futures).

7 Likes