The Easiest Patterns in Rust

A few software design patterns are seamlessly integrated into Rust, so we're using them without realizing them. Which is great! A hint: it is 2 creational patterns.

The Easiest Patterns in Rust
Photo by Hello I'm Nik / Unsplash

Previously, I wrote about the most difficult to implement software design pattern in Rust: Mediator pattern in Rust.

Now, let's talk about what is, in my opinion, the easiest patterns in Rust (from 23 classic design patterns).

Guess what the pattern is this:

impl Circle {
    pub fn new(radius: u32) -> Circle {
        Self { radius }
    }
}

You likely wrote this code a million times in Rust.

πŸŒ€ It is... πŸ₯πŸ₯πŸ₯ A Static Creation Method (a little relative of a Factory Method).

There is a notion of "constructor" in a typical OOP language which is a default class method to create an object. Not in Rust: constructors are thrown away because there is nothing that couldn't be achieved with a static creation method.

In a typical constructor idiom, there is no way to gracefully handle a construction error, there are also limitations about the complexity of the construction code. On the other hand, there could be any number of static methods with any kind of complex logic, e.g. loading from a database.

There are a few ways to define a static creation method.

  1. A default() method from Default trait for construction with no parameters. Use either default #[derive(Default)], or a manual trait implementation.
#[derive(Default)]
struct Circle;

let circle = Circle::default();

2. A handwritten new() method for a custom object creation with parameters:

impl Rectangle {
    pub fn new(width: u32, length: u32) -> Rectangle {
        Self { width, length }
    }
}

let rectangle = Rectangle::new(10, 20);

3. A from_ prefixed method for constructing from a custom object (you can use any name, however, it's kind of a naming convention).

let circle = Circle::from_shape(shape, 10, 20);

4. Implement a From<> trait for constructing from a known type.

impl From<PriceData> for FormattedData {
    fn from(price: PriceData) -> FormattedData {
        FormattedData {
            text: format!("{} {}", price.one, price.two),
        }
    }
}

let formatted_data = price_data.into();

Alright!

Now, guess what the pattern is represented by the following snippet:

#[derive(Clone)]
struct Rectangle {
    width, height: u32,
}

let rectangle1 = Rectangle::new(10, 20);
let mut rectangle2 = rectangle1.clone();
rectangle2.set_width(50);

πŸŒ€ It is a Prototype, a creational pattern that allows you to copy existing objects without depending on their types.

Rust has standard Clone implementation (via #[derive(Clone)]) for many types which makes Prototype easy and seamless to use.

The point is that some patterns must not be programmed in a too overcomplicated manner having convenient instruments coming with language. And it's cool having a piece of knowledge about 2 creational design patterns "out-of-the-box" with knowing Rust.


As an outro, let me put a quote about the topic from Linkedin (by MichaΕ‚ Isalski):

"Design patterns are methods of achieving features that are not built-in into the language itself. In the old days there was a "Variable" design pattern, used in ASM. Guess what - now all languages have that built-in. The same case is shown here - Rust makes some patterns into no-ops, while others (memory-unsafe ones) into hard-to-achieve.

Additional thanks to Oliver Marc S. for expanding the topic of the static creation method.