Object creation with traits and generics
The Factory pattern encapsulates object creation logic, allowing you to create objects without specifying their concrete types. In Rust, we adapt this pattern using traits and associated functions rather than traditional OOP inheritance.
When to use factories in Rust:
use std::fmt::Debug;
/// Trait defining the interface for all shapes
pub trait Shape: Debug {
fn area(&self) -> f64;
fn perimeter(&self) -> f64;
fn name(&self) -> &str;
}
/// Concrete implementation: Circle
#[derive(Debug)]
pub struct Circle {
radius: f64,
}
impl Shape for Circle {
fn area(&self) -> f64 {
std::f64::consts::PI * self.radius * self.radius
}
fn perimeter(&self) -> f64 {
2.0 * std::f64::consts::PI * self.radius
}
fn name(&self) -> &str {
"Circle"
}
}
/// Concrete implementation: Rectangle
#[derive(Debug)]
pub struct Rectangle {
width: f64,
height: f64,
}
impl Shape for Rectangle {
fn area(&self) -> f64 {
self.width * self.height
}
fn perimeter(&self) -> f64 {
2.0 * (self.width + self.height)
}
fn name(&self) -> &str {
"Rectangle"
}
}
/// Concrete implementation: Triangle
#[derive(Debug)]
pub struct Triangle {
a: f64,
b: f64,
c: f64,
}
impl Shape for Triangle {
fn area(&self) -> f64 {
// Heron's formula
let s = (self.a + self.b + self.c) / 2.0;
(s * (s - self.a) * (s - self.b) * (s - self.c)).sqrt()
}
fn perimeter(&self) -> f64 {
self.a + self.b + self.c
}
fn name(&self) -> &str {
"Triangle"
}
}
/// Simple factory function
pub fn create_shape(shape_type: &str, params: &[f64]) -> Option<Box<dyn Shape>> {
match shape_type {
"circle" => {
if params.len() >= 1 {
Some(Box::new(Circle { radius: params[0] }))
} else {
None
}
}
"rectangle" => {
if params.len() >= 2 {
Some(Box::new(Rectangle {
width: params[0],
height: params[1],
}))
} else {
None
}
}
"triangle" => {
if params.len() >= 3 {
Some(Box::new(Triangle {
a: params[0],
b: params[1],
c: params[2],
}))
} else {
None
}
}
_ => None,
}
}
/// Abstract Factory using trait objects
pub trait ShapeFactory {
fn create(&self) -> Box<dyn Shape>;
}
/// Factory for creating circles of a specific size
pub struct CircleFactory {
pub default_radius: f64,
}
impl ShapeFactory for CircleFactory {
fn create(&self) -> Box<dyn Shape> {
Box::new(Circle { radius: self.default_radius })
}
}
/// Factory for creating squares (special rectangles)
pub struct SquareFactory {
pub side: f64,
}
impl ShapeFactory for SquareFactory {
fn create(&self) -> Box<dyn Shape> {
Box::new(Rectangle {
width: self.side,
height: self.side,
})
}
}
/// Generic factory using closures
pub struct FunctionFactory<F>
where
F: Fn() -> Box<dyn Shape>,
{
factory_fn: F,
}
impl<F> FunctionFactory<F>
where
F: Fn() -> Box<dyn Shape>,
{
pub fn new(factory_fn: F) -> Self {
FunctionFactory { factory_fn }
}
pub fn create(&self) -> Box<dyn Shape> {
(self.factory_fn)()
}
}
/// Factory with configuration
#[derive(Debug, Clone)]
pub struct ShapeConfig {
pub scale: f64,
pub default_color: String,
}
impl Default for ShapeConfig {
fn default() -> Self {
ShapeConfig {
scale: 1.0,
default_color: "black".to_string(),
}
}
}
pub struct ConfigurableShapeFactory {
config: ShapeConfig,
}
impl ConfigurableShapeFactory {
pub fn new(config: ShapeConfig) -> Self {
ConfigurableShapeFactory { config }
}
pub fn create_circle(&self, radius: f64) -> Circle {
Circle {
radius: radius * self.config.scale,
}
}
pub fn create_rectangle(&self, width: f64, height: f64) -> Rectangle {
Rectangle {
width: width * self.config.scale,
height: height * self.config.scale,
}
}
}
/// Static factory methods (Rust idiom: associated functions)
impl Circle {
pub fn new(radius: f64) -> Self {
Circle { radius }
}
pub fn unit() -> Self {
Circle { radius: 1.0 }
}
}
impl Rectangle {
pub fn new(width: f64, height: f64) -> Self {
Rectangle { width, height }
}
pub fn square(side: f64) -> Self {
Rectangle { width: side, height: side }
}
}
fn main() {
// Simple factory function
let circle = create_shape("circle", &[5.0]).unwrap();
let rect = create_shape("rectangle", &[4.0, 3.0]).unwrap();
println!("{}: area = {:.2}", circle.name(), circle.area());
println!("{}: area = {:.2}", rect.name(), rect.area());
// Abstract factory
let factories: Vec<Box<dyn ShapeFactory>> = vec![
Box::new(CircleFactory { default_radius: 10.0 }),
Box::new(SquareFactory { side: 5.0 }),
];
for factory in &factories {
let shape = factory.create();
println!("Created {}: area = {:.2}", shape.name(), shape.area());
}
// Closure-based factory
let custom_factory = FunctionFactory::new(|| {
Box::new(Triangle { a: 3.0, b: 4.0, c: 5.0 })
});
let triangle = custom_factory.create();
println!("{}: area = {:.2}", triangle.name(), triangle.area());
// Configurable factory
let config = ShapeConfig { scale: 2.0, default_color: "blue".to_string() };
let factory = ConfigurableShapeFactory::new(config);
let scaled_circle = factory.create_circle(5.0);
println!("Scaled circle radius: {}", scaled_circle.radius); // 10.0
// Static factory methods
let unit_circle = Circle::unit();
let square = Rectangle::square(4.0);
println!("Unit circle area: {:.2}", unit_circle.area());
println!("Square area: {:.2}", square.area());
}
Box): Enable runtime polymorphism| Pattern | Use Case | Rust Implementation |
|---------|----------|---------------------|
| Simple Factory | String → concrete type | Function returning Box |
| Factory Method | Subclass decides type | Trait with create() method |
| Abstract Factory | Family of related objects | Trait returning related trait objects |
| Static Factory | Named constructors | Associated functions (impl Type) |
// DON'T: Factory that just calls new()
fn useless_factory() -> MyType {
MyType::new() // Just call new() directly
}
// DON'T: Overly complex factory for simple types
struct OverEngineeredFactory;
impl OverEngineeredFactory {
fn create_string(&self) -> String {
String::new() // Just use String::new()
}
}
// DO: Use factory when there's real logic
fn create_connection(config: &Config) -> Result<Connection, Error> {
// Validation, retry logic, etc.
}
Deserialize trait creates types from various formatsClient::builder() for HTTP client configurationShapeRegistry that maps string names to factory functionsResultRun this code in the official Rust Playground