So I saw this post asking for a translation of these Java design patterns to Rust. I will present a quite literal translation of the factory pattern and comment on its Rust idiomaticity in the progress. You can find the full code here. Be sure to have a look at all the commits.

The Java Factory Pattern

This section uses Java terminology.

The example contains an interface Shape with the method draw. Three classes (Circle, Square, Rectangle) implement that interface. Now I don’t find this example very well-chosen, as one could argue wether the Square class should be a subclass of the Rectangle class. In the example they are not subclasses and the geometrical relation between squares and rectangles is not used in any way. So to remove unnecessary confusion I will replace Square by Triangle in the following.

Furthermore we have a class ShapeFactory with a non-static method getShape, which receives a String and creates an instance of a class for it (e.g. String "circle" will result in a Circle object being returned). Then there is the main class FactoryPatternDemo, which serves the sole purpose of containing the main method, the start of the execution.

The Literal Translation

Shapes

A Java “interface” can be translated to a Rust “trait”. They both define common methods to interact with different things.

A Java “interface” defines “methods”. “Methods” are functions, which always get a reference to an instance of their associated class as the first parameter “this”. If a “class” has all these methods, with the same type signature as defined by the interface, the class “implements” that interface (you also have to declare that it does so).

A Rust “trait” defines functions. These functions often take as first argument a self in some way (as a reference &self, as a mutable reference &mut self or by taking complete ownership self). This is optional tough. If there is an “implementation” of a trait for a “struct” (or “enum”), that “struct” (or “enum”) “satisfies” that trait.

So let’s look at some code:

trait Shape {
    fn draw(&self);
}
 
struct Rectangle;
struct Circle;
struct Triangle;
 
impl Shape for Rectangle {
    fn draw(&self) {
        println!("Inside Rectangle::draw function");
    }
}
 
impl Shape for Circle {
    fn draw(&self) {
        println!("Inside Circle::draw function");
    }
}
 
impl Shape for Triangle {
    fn draw(&self) {
        println!("Inside Triangle::draw function");
    }
}

The lines with struct would usually contain information about the representation of the respective objects. A more realistic example could look like this:

struct Rectangle {
    origin: (f64,f64),
    size: (f64,f64),
}

But this is besides the point, as the original Java pattern PDF also uses dummy objects without member data.

The Factory

In the Java version there is a class ShapeFactory with a method getShape which returns an object of type Shape

Java uses dynamic dispatch by default. This means I can have a variable with the type Shape and it will check at runtime which draw function to call. Rust doesn’t like to do things at runtime (because it makes program execution slower), so dynamic dispatch is not enabled by default.

In Rust, there can’t be a variable with the type Shape! But Rust supports dynamic dispatch, if we really want it. We just have to box the variable: A variable of type Box<Shape> is perfectly fine.

But there is another pitfall with the return type of getShape: If the given string matches none of the available shapes, the factory will return null. In Java every function which is supposed to return an object can return null instead. In Rust we are only allowed to do that if we explicitly say so in the type of the function. Instead of returning a Thing we must return an Option<Thing> (Option documentation). So the Java return type of Shape becomes Option<Box<Shape>>.

struct ShapeFactory;
 
impl ShapeFactory {
    pub fn new() -> ShapeFactory {
        ShapeFactory
    }
    pub fn get_shape(&self, shape_type: &str) -> Option<Box<Shape>> {
        let shape_type_uppercase = shape_type.to_uppercase();
        match shape_type_uppercase.as_ref() {
            "CIRCLE" => Some(Box::new(Circle)),
            "RECTANGLE" => Some(Box::new(Rectangle)),
            "TRIANGLE" => Some(Box::new(Triangle)),
            _ => None,
        }
    }
}

The Main Function

In Java there are no functions. Everything is a method. To start execution, there is a class which has a method named main. Execution will start there. In Rust execution starts at the function named main. I will not literally translate this Java-madness OOP-for-the-sake-of-it main method to a Rust struct, but instead I will keep my sanity and translate the Java main method to the Rust main function.

Also instead of implicitly breaking the whole program in the case that we get a None (Rust’s version of Java’s null), we actually have to handle that case in Rust, because Rust does not like failing implicitly.

fn main() {
    let shape_factory : ShapeFactory = ShapeFactory::new();
    let shape1_option : Option<Box<Shape>> = shape_factory.get_shape("CIRCLE");
    match shape1_option {
        Some(shape1) => {
            // shape1 has type Box<Shape>
            shape1.draw();
        }
        None => {
            panic!("No shape found!");
        }
    }
}

Splitting it up

In the Java example everything is split up in multiple files. We just put the code in one file so far. Let’s change that.

Making it More Idiomatic

Throwing out the Factory

In the above version, we use the ShapeFactory as an object. It has only one method and no internal state. In rust we would definitely just use a function for that and throw out the ShapeFactory.

pub fn get_shape_by_name(shape_type: &str) -> Option<Box<Shape>> {
    let shape_type_uppercase = shape_type.to_uppercase();
    match shape_type_uppercase.as_ref() {
        "CIRCLE" => Some(Box::new(Circle)),
        "RECTANGLE" => Some(Box::new(Rectangle)),
        "TRIANGLE" => Some(Box::new(Triangle)),
        _ => None,
    }
}

Or even not have that function at all and just directly construct what you want (e.g. Rectangle). To create more complicated things, we use the builder pattern in Rust.

Throwing out Dynamic Dispatch

Dynamic dispatch is a nice feature and in most situations its performance penalty is not even noticeable. If execution speed is of importance, we want to avoid dynamic dispatch. Rust provides means to write generic code but at the same time avoid dynamic dispatch.

fn show_shape<T:Shape>(shape: &T) {
    println!("Have a look at this marvelous shape:");
    shape.draw();
    println!("Amazing!");
}
 
fn main() {
    let shape2 = triangle::Triangle;
    show_shape(&shape2);
}

The function show_shape is defined for any type that satisfies the trait Shape. The compiler will see what type the actually used shape object has and select the respective function call at compile-time! It cannot be used on a Box<Shape>! Repeat, the following does not work:

// shape1 has type Box<Shape>
show_shape(&shape1);

Summary

Rust supports many different programming styles. You can program very javaesque code with it and it won’t complain. It will just run a little slower, which is probably fine for 99% of all code. If you need some fast code, don’t try to translate what you would do in another language to Rust; read about Rust, read Rust code, write Rust code and learn to think in a Rusty way for optimal results!