Extending behaviour with composite types and Deref

⚓ Rust    📅 2026-01-04    👤 surdeus    👁️ 2      

surdeus

Hello,

I was hoping to get some advice on API design, which I'm struggling with coming from an OOP background in C++.

I am writing a library that helps to build a type of agent-based model called CPM.
The structure of a fully implemented CPM model using this library would be something like:

struct Model {
    pond: Pond,
    ...
}

struct Pond {
    environment: Environment,
    potts: Potts,
    ...
}

I hope to provide many commonly needed operations for Pond, Environment, and Potts, while still supporting easy extension of behaviour so that users of the lib can implement their desired model features (if you are not familiar with scientific modeling, it's something like using a game engine).

Doing that for Potts is easy. Because a Potts object is just the implementation of an algorithm, Ive made a PottsAlgorithm trait that has a bunch of default methods and can be easily implemented for any concrete Potts object.

I am struggling with Pond and Environment, however. These types need to hold a significant number of data fields for their methods to work, such that implementing the logic as traits would also require the implementation of many accessor methods like Environment::cells(), Environment::cell_lattice() etc.

My current solution is providing BasePond and BaseEnvironment types, which implement essential behaviour for a CPM simulation to run and can be extended through composition.

This makes sense in my head because BaseEnvironment and BasePond are concrete types with concrete objectives: they manage an environment where cells live in isolation. Thus, when extending the behaviour of these types, I find myself often doing some prior os posterior operation and then delegating to BaseEnvironment or BasePond. For example:

struct MyEnvironment {
    base_env: BaseEnvironment,
    other_object: OtherObj
} 

impl MyEnvironment {
    fn spawn_cell(&self) {
        self.other_object.do_something();
        self.base_env.spawn_cell();
    }
}

The problem with this pattern is that it leads to some very long object paths. For example, I commonly find myself writing my_pond.base_pond.my_environment.base_environment. One way to solve this would be implementing Deref<BaseEnvironment> for MyEnvironment and Deref<BasePond> for MyPond, but it seems to me that these traits are implemented for pointer-like structs (see Deref in std::ops - Rust and Understanding the perils of `Deref`).

What do you guys think, is it ok to use composite types to extend behaviour (and is it ok to implement Deref in that case), or should this be done only with traits?

2 posts - 2 participants

Read full topic

🏷️ Rust_feed