Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Inner Trait pattern #355

Closed
wants to merge 14 commits into from
2 changes: 2 additions & 0 deletions src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,11 @@
- [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)
- [Sealed Trait](./patterns/behavioural/sealed.md)
- [Strategy](./patterns/behavioural/strategy.md)
- [Visitor](./patterns/behavioural/visitor.md)
- [Creational](./patterns/creational/intro.md)
Expand Down
96 changes: 96 additions & 0 deletions src/patterns/behavioural/inner-trait.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
# 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`.

```rust,ignore
// trait that is public and part of the API
pub trait Car {
fn speed(&self) -> f64;
fn accelerate(&mut self, duration: f64);
}
//not public
mod inner_lib {
// trait that is only accessible to this crate
pub(crate) trait InnerCar {
fn speed(&self) -> f64;
//private
fn set_speed(&mut self, new_speed: f64);
//private
fn acceleration(&self) -> f64;
fn accelerate(&mut self, duration: f64) {
self.set_speed(
self.speed() + (self.acceleration() * duration)
);
}
}
//Auto implement Car for all InnerCar, by forwarding the Car trait to the
//InnerCar implementation
impl<T: InnerCar> crate::Car for T {
fn speed(&self) -> f64 {
<Self as InnerCar>::speed(self)
}
fn accelerate(&mut self, duration: f64) {
<Self as InnerCar>::accelerate(self, duration)
}
}
}

#[derive(Default)]
pub struct Car1(f64);
//is not necessary to implement `accelerate`, as inner_trait can do that.
impl inner_lib::InnerCar for Car1 {
fn speed(&self) -> f64 {self.0}
fn set_speed(&mut self, new_speed: f64) {self.0 = new_speed}
fn 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.

## 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.
104 changes: 104 additions & 0 deletions src/patterns/behavioural/sealed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
# Sealed Trait

## Description

In Rust, a sealed trait is a trait that can only be implemented within the same
crate where it is defined. It is achieved by making a public trait that depends
on a [supertrait](https://doc.rust-lang.org/rust-by-example/trait/supertraits.html)
that is private or only public on the local crate. This restricts the ability
to implement the trait from outside the crate and provides a form of interface
segregation.

## Motivation

Sealed traits can be used to create a set of behaviors that that allow future
addition of methods without breaking the API.

The sealed trait pattern helps to separate the public interface from the
implementation details. This makes it easier to maintain and evolve the
implementation over time without breaking existing code. It also provides a
level of abstraction that allows users to interact with the library or module at
a high level, without needing to know the implementation details.

Because users of this crate can't implement this trait directly, is possible to
add methods to it, without break existing code using this create. Only
implementations of this trait will need updated, what is assured by the
sealed trait to only happen locally.

## Example

One possible use of the sealed trait is to limit what kind of implementation a
function can receive, allowing only a limited number of types to be passed as
parameters.

```rust,ignore
pub(crate) mod private {
pub(crate) trait Sealed {}
}
// MyStruct is Sealed, and only this crate have access to it. Other crates will
// be able to implement it.
pub trait MyStruct: private::Sealed {...}
// auto implement Sealed for any type that implement MyStruct
impl<T: MyStruct> private::Sealed for T {}

pub struct MyStructA {...}
impl MyStruct for MyStructA {...}

pub struct MyStructB {...}
impl MyStruct for MyStructB {...}

// this function will only receive MyStructA or MyStructB because they are the
// only ones that implement the MyStruct trait
pub fn receive_my_struct(my_struct: impl MyStruct) {...}
```

The standard library makes use of a sealed trait, one example is the
`OsStrExt` trait for
[unix](https://doc.rust-lang.org/std/os/unix/ffi/trait.OsStrExt.html),
[windows](https://doc.rust-lang.org/std/os/windows/ffi/trait.OsStrExt.html) and
[wasi](https://doc.rust-lang.org/std/os/wasi/ffi/trait.OsStrExt.html).

Trait from `std::os::unix::ffi::OsStrExt`:

```rust,ignore
pub trait OsStrExt: Sealed {
fn from_bytes(slice: &[u8]) -> &Self;
fn as_bytes(&self) -> &[u8];
}
```

The `Sealed` trait is private and cannot be accessed from outside the standard
library. Not allowing users to implement `OsStrExt` for any type, except for the
implementations already present on the standard library.

The documentation describes it's motivation as the following:

> This trait is sealed: it cannot be implemented outside the standard library.
> This is so that future additional methods are not breaking changes.

## Advantages

By separating the public interface from the implementation details, it is
easier to maintain, ensure that the code remains correct without affecting
external users.

## Disadvantages

Although it usually reduces complexity and code duplication, the sealed trait
pattern can add complexity to the codebase, particularly if there are many
sealed traits that need to be managed.

## Discussion

Sealed traits are a useful tool for creating a set of related behaviors that are
intended to be used together without allowing other behaviors to be added from
outside the crate.

This restriction also allow future additions to the trait
without compromising the compatibility with existing code uses of it.

## See also

Blog post from
[Predrag](https://web.archive.org/web/20230406211349/https://predr.ag/blog/definitive-guide-to-sealed-traits-in-rust/)
about sealed, private and other pattern for traits.
Comment on lines +1 to +104
Copy link
Collaborator

Choose a reason for hiding this comment

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

This file belongs to #354 as well. Do you want to PR it all in one PR, or you rather want it separated? If so, then removal of this file would be good.