Thanks to the very powerful Enum types in Rust, it is easy to define a function that can return different types in a safe manner. Let’s run through a very basic example highlighting the approach.
Let’s imagine two very simple struct
, each representing a geometric shape: Circle
or a Square
. They both have a constructor that requires a single parameter of type f64
, and each implements a method that the other does not, respectively area
and perimeter
. Here is the code:
use std::f64::consts::PI;
struct Square {
side: f64
}
impl Square {
pub fn new(side : f64) -> Self {
Self{side}
}
pub fn perimeter(&self) -> f64 {
4f64 * self.side
}
}
struct Circle {
radius: f64
}
impl Circle {
pub fn new(radius : f64) -> Self {
Self{radius}
}
pub fn area(&self) -> f64 {
PI * self.radius.powf(2f64)
}
}
We would like to implement a function called build_shape
that takes two arguments: a string describing the shape to be built (“Circle” or “Square”) and a parameter value of type f64
to pass to the corresponding constructor. The function will return an instance of the type suggested by the name. Since both structs implement different methods, we could not constrain the return type of our function to a single Trait
. Instead we will declare an enum
that can internally pack instances of each type (which we will call Shape
) and return an instance with our function:
enum Shape {
Circle(Circle),
Square(Square)
}
fn build_shape(name: &str, param: f64) -> Shape {
match name{
"Circle" => Shape::Circle(Circle::new(param)),
"Square" => Shape::Square(Square::new(param)),
_ => panic!("Shape unknown")
}
}
The Shape
instance can then simply be unpacked appropriately using a match
expression. Here is an example of its use in a main
function:
fn main() {
match build_shape("Circle", 1f64) {
Shape::Circle( obj ) => println!("Area: {}", obj.area()),
Shape::Square( obj ) => println!("Perimeter: {}", obj.perimeter())
}
}
// Returns Area: 3.141592653589793
This Rust enum
type seems very much inspired by the Algebraic Data Types that exist in Functional Programming (FP) languages like Haskell or OCaml. It is less powerful than its FP counterterparts but nonetheless very useful for handling Factory type functionality efficiently.
If you like this post, don’t forget to follow me on Twitter and get notified of the next publication.