Photo by Ananthu Ananthu: https://www.pexels.com/photo/car-start-button-in-close-up-photography-12551173/

Easy delegation in Rust: the delegation pattern

Photo by Thirdman: https://www.pexels.com/photo/person-handing-out-keys-to-another-person-8482876/

Introduction

In delegation, you delegate a certain request to an object to a second object, which we call the delegate. This is almost like a subclass sending a request to its parent class. In Rust we can achieve this by embedding the delegate, that is the object to which the original request is delegated, in the struct.

A best practice is, is that both the enclosing struct and the delegate implement the same trait.

Implementation in Rust

In this example we will implement a virtual UI with a TextWindow which has a width and a height, both are integer. For our UI it is important to know that area of the window.

We will start by implementing the Bounds trait which has one method:

trait Bounds {
    fn area(&self) -> i32;
}

Note that because this method does not change the state of the implementing struct, we do not need &mut self.

Next we implement the Rectange struct:

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

impl Rectangle {
    fn new(width: i32,height: i32)->Self {
        Rectangle {
            width,
            height,
        }
    }
}

impl Bounds for Rectangle {
    fn area(&self) -> i32 {
        self.width*self.height
    }
}

Some remarks:

  1. A rectangle in our example just has an integer width and an integer height
  2. We also implement a simple constructor-like method
  3. Note that we need to make sure that this struct is cloneable. This is needed for the embedding in the TextWindow struct later on
  4. Implementing the Bounds interface consisted of implementing one method without any side-effects.

Then we come to already mentioned TextWindow struct:

struct TextWindow {
    title: String,
    window: Rectangle
}

impl TextWindow {
    fn new(title: &str,rectangle: &Rectangle) -> Self {
        TextWindow {
            title: title.to_string(),
            window: rectangle.clone()
        }
    }
}

impl Bounds for TextWindow {
    fn area(&self) -> i32 {
        self.window.area()
    }
}

Line by line:

  1. A textwindow in our example consists of a title string, and a rectangle called window.
  2. Like in Rectangle we also implement a constructor-like method
  3. Note that we clone the supplied Rectangle struct, because TextWindow talkes ownership of the supplied struct. If you want to avoid cloning, you would need to change the ownership structure of your TextWindow to store a reference to the Rectangle, but that would introduce lifetime constraints, and you wouldn’t be able to implement the Bounds trait as easily. Cloning is a valid approach in this case to simplify ownership.
  4. In the implementation of the Bounds trait, the call to area() is delegated to an instance of the Rectangle struct. This is where the delegation happens.

Time to test

Now we can test our setup:

fn main() {
    let rectangle=Rectangle::new(80,40);
    let window=TextWindow::new("my window",&rectangle);
    println!("The area is {} for window with title {}", window.area(),window.title);
}

Line by line:

  1. We create a new Rectangle struct
  2. We pass that along with a title to the constructor of the TextWindow
  3. Then we simply print out the title and the area.

Conclusion

Implementing this pattern was relatively easy. In a later post I will probably develop a more involved example, where the power of this relatively simple pattern becomes even clearer.

Leave a Reply

Your email address will not be published. Required fields are marked *