diff --git a/src/SUMMARY.md b/src/SUMMARY.md index c2771940..1fa528e4 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -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) diff --git a/src/patterns/behavioural/inner-trait.md b/src/patterns/behavioural/inner-trait.md new file mode 100644 index 00000000..06532653 --- /dev/null +++ b/src/patterns/behavioural/inner-trait.md @@ -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 crate::Car for T { + fn speed(&self) -> f64 { + ::speed(self) + } + fn accelerate(&mut self, duration: f64) { + ::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. diff --git a/src/patterns/behavioural/sealed.md b/src/patterns/behavioural/sealed.md new file mode 100644 index 00000000..45b90f86 --- /dev/null +++ b/src/patterns/behavioural/sealed.md @@ -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 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.