Reimplement Modern C++ Design in Rust: Policy and Policy Classes

2024-07-21
Forward links: related:C++ related:Rust Backward links:

I'm reading Andrei Alexandrescu: Modern C++ design: Policy-Based Class Design(Alexandrescu 2001) recently, and I think it might be a good idea to RiiR just for better learning experience.

Policy

As the author has pointed out, policies are mostly just traits. In C++ it is implemented as … nothing, just a textual declaration about the interface. Later on their implementations (i.e policy classes) are used as template parameters, which means policies

  • get keyword generic (Wuyts 2022) for free. Since Rust is working on it, so let's forget about this part.
  • can store states. I'd say it is a bad design, so let's forget about this part too.

Other than that, it is simply

trait SomePolicy {
    fn some_interface();
}

Policy class

Policy classes are the concrete implementation of policies, so just structs and enums that implements one:

struct SomeStruct {
}

impl SomePolicy for SomeStruct {
    fn some_interface() {
        todo!()
    }
}

Host

Host multi-inherits or contains one to several policy implementations (in generic way). Since Rust deliberately forbids multi-inheritance, which is a good design choice, let's just use composition:

struct SomeHost<T: SomePolicy> {
    policy_class: T,
}

Example from the book

The aforementioned concept mapping into Rust seems pretty straightforward. Now let's try to implement the example in the book to see what the code actually looks like. Details are omitted, just to get a quick broad sense on this pattern.

So in the book there is a Creator policy. It has a simple interface create which returns something as a pointer. For simplicity let's change the interface a little bit to have the constructed value moved in.

trait Creator {
    type Item;
    type Ptr<X>;
    fn create(v: Self::Item) -> Self::Ptr<Self::Item>;
}

Here generic associated type is used to declare Ptr, which is introduced in Rust 1.65.

Then, a policy class:

#[derive(Default)]
struct BoxCreator<T> {
    _v: PhantomData<T>,
}

impl<T> Creator for BoxCreator<T> {
    type Item = T;
    type Ptr<X> = Box<X>;
    fn create(v: Self::Item) -> Self::Ptr<Self::Item> {
        Box::new(v)
    }
}

Explicit item type host

I think in Rust there is no way you can directly pass type constructor as generic types in struct. If I'm wrong please tell me.

Anyway, for the version that the host library leaves the choice of Item to the client, it is

#[derive(Default)]
struct Host<T, P> {
    _item: PhantomData<T>,
    _creator: PhantomData<P>,
}

impl<T, P: Creator<Item = T>> Host<T, P> {
    fn build(v: P::Item) -> P::Ptr<P::Item> {
        P::create(v)
    }
}

In client code,

type MyI32Manager = Host<i32, BoxCreator<i32>>;
// usage: `MyI32Manager::build(42)`

For the version that the host library kept the choice of Item inside, one simple way is just

type I32Host<P> = Host<i32, P>;

In client code,

type MyI32Manager = I32Host<BoxCreator<i32>>;
// usage: `MyI32Manager::build(42)`

Although we still need to specify BoxCreator's type argument, if we declare I32Host<BoxCreator<i64>> for example and used the type in actual code, an compile error is emitted at the caller site. With the aspect of type-safety it is good enough.

"non-explicit" item type host

Actually T is not needed in the Host declaration. For the version leaves the choice of Item to the client:

#[derive(Default)]
struct Host<P> {
    _creator: PhantomData<P>,
}

impl<P: Creator> Host<P> {
    fn build(v: P::Item) -> P::Ptr<P::Item> {
        P::create(v)
    }
}

In client code,

type MyI32Manager = Host<BoxCreator<i32>>;

To force the client to obey the host library's decision of Item:

#[derive(Default)]
struct Host<P> {
    _creator: PhantomData<P>,
}

impl<P: Creator<Item = i32>> Host<P> {
    fn build(v: P::Item) -> P::Ptr<P::Item> {
        P::create(v)
    }
}

I's just one line of code: Specify the associated type of Creator.

In client code the usage is the same.

Reference

Alexandrescu, Andrei. 2001. Modern c++ Design: Generic Programming and Design Patterns Applied. Addison-Wesley Professional.
Wuyts, Yoshua. 2022. “Announcing the Keyword Generics Initiative.” July 27, 2022. https://blog.rust-lang.org/inside-rust/2022/07/27/keyword-generics.html.