Skip to content
Closed
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
- [Design Patterns](./patterns/index.md)
- [Behavioural](./patterns/behavioural/intro.md)
- [Command](./patterns/behavioural/command.md)
- [Inner Trait](./patterns/behavioural/inner-trait.md)
- [Interpreter](./patterns/behavioural/interpreter.md)
- [Newtype](./patterns/behavioural/newtype.md)
- [RAII Guards](./patterns/behavioural/RAII.md)
Expand Down
105 changes: 105 additions & 0 deletions patterns/behavioural/inner-trait.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
# Inner Trait

## Description

It is possible to define a private trait that implements all the
methods of a public trait and also includes some private functions. This pattern
can be used to provide additional functionality to the implementation of a
public trait while keeping the private methods hidden from the public API.

## Example

This example demonstrate how a public trait `Car` can be implemented and include
extra private methods using a auxiliary private trait `InnerCar`.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we probably want to show a real example in standard library or in popular crates for this.

```rust,ignore
// trait that is public and part of the API
pub trait Car {
fn get_speed(&self) -> f64;
fn accelerate(&mut self, duration: f64);
fn brake(&mut self, force: f64);
Copy link
Contributor

@pickfire pickfire Apr 7, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would recommend keeping this to at most 2 functions to make a minimal example. And also, car might not be a good example, since usually car isn't considered a trait, vehicle probably is, depending on the context.

}
//not public
mod inner_lib {
// trait that is only accessible to this crate
pub(crate) trait InnerCar {
fn get_speed(&self) -> f64;
//private
fn set_speed(&mut self, new_speed: f64);
//private
fn get_acceleration(&self) -> f64;
fn accelerate(&mut self, duration: f64) {
self.set_speed(
self.get_speed() + (self.get_acceleration() * duration)
);
}
fn brake(&mut self, force: f64) {
self.set_speed(
self.get_speed() - (force * self.get_acceleration())
);
}
}
//Auto implement Car for all InnerCar, by forwarding the Car trait to the
//InnerCar implementation
impl<T: InnerCar> crate::Car for T {
fn get_speed(&self) -> f64 {
<Self as InnerCar>::get_speed(self)
}
fn accelerate(&mut self, duration: f64) {
<Self as InnerCar>::accelerate(self, duration)
}
fn brake(&mut self, force: f64) {
<Self as InnerCar>::brake(self, force)
}
}
}
#[derive(Default)]
pub struct Car1(f64);
//is not necessary to implement `accelerate` and `brake`, as inner_trait can do that.
impl inner_lib::InnerCar for Car1 {
fn get_speed(&self) -> f64 {self.0}
fn set_speed(&mut self, new_speed: f64) {self.0 = new_speed}
fn get_acceleration(&self) -> f64 {0.10}
}
//more Car implementations...
```

## Motivation

This pattern allows developers to provide additional functionality to the
implementation of a public trait without exposing that functionality as part of
the public API. By using a private trait, developers can also improve the
reusability of their code, since the private functionality can be reused across
multiple implementations of the public trait.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suppose I'm using a trait Car from another crate vehicles, but for a very specific purpose requiring some additional code. Do you feel that a new trait InnerCar private to my own crate would fall under this pattern? If yes, this would be an alternative motivation, wouldn't it?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is another layer on top of this is to prevent breaking changes, since we can now limit functionality without exposing public API.


## Advantages

- Provides hidden functionality while keeping the private methods from the API.
- Improves modularity by separating public and private functionality.
- Increases code reusability, since the private functionality can be reused.

## Disadvantages

- Can be harder to understand if the private trait are not well documented.
- Can lead to tight coupling between the public and private functionality.

## Discussion

This pattern is very similar to the concept of "interfaces" and "abstract types"
with public/private methods in object-oriented programming.

In object-oriented programming (OOP), private methods can be used to encapsulate
implementation details within an interface, while public methods exposes
functionality.

In rust there is the public/private trait analogous, the private trait
implementing the hidden functionalities, while the public trait exposes
functionality.

## See also

Wikipedia [OOP Interface](https://en.wikipedia.org/wiki/Interface_%28object-oriented_programming%29).

Blog post from [Predrag](https://predr.ag/blog/definitive-guide-to-sealed-traits-in-rust/)
about sealed, private and other patterns for traits.