From 1fca6f4093541ae88d23b5bfc245aceed2ca2be3 Mon Sep 17 00:00:00 2001 From: jhwgh1968 Date: Sat, 3 Apr 2021 16:37:27 -0500 Subject: [PATCH 01/13] Add Functional - Generics as Type Classes --- SUMMARY.md | 1 + functional/generics-type-classes.md | 268 ++++++++++++++++++++++++++++ 2 files changed, 269 insertions(+) create mode 100644 functional/generics-type-classes.md diff --git a/SUMMARY.md b/SUMMARY.md index 41191739..abce59cb 100644 --- a/SUMMARY.md +++ b/SUMMARY.md @@ -46,6 +46,7 @@ - [Functional Programming](./functional/index.md) - [Programming paradigms](./functional/paradigms.md) + - [Generics as Type Classes](./functional/generics-type-classes.md) - [Additional Resources](./additional_resources/index.md) - [Design principles](./additional_resources/design-principles.md) diff --git a/functional/generics-type-classes.md b/functional/generics-type-classes.md new file mode 100644 index 00000000..2df98f01 --- /dev/null +++ b/functional/generics-type-classes.md @@ -0,0 +1,268 @@ +# Generics as Type Classes + +## Description + +Rust's type system is designed more like functional languages (like Haskell) +rather than imperative languages (like Java and C++). As a result, Rust can turn +many kinds of programming problems into "static typing" problems. This is one +of the biggest wins of choosing a functional language, and is critical to many +of Rust's compile time guarantees. + +A key part of this idea is the way generic types work. In C++ and Java, for +example, generic types are a meta-programming construct for the compiler. +`Vec` and `Vec` in C++ are just two different copies of the same +boilerplate code for a `Vec` type (known as a `template`) with two +different types filled in. + +In Rust, a generic type parameter creates what is known in functional +languages as a "type class constraint", and each different parameter filled in +by an end user *actually changes the type*. In other words, `Vec` and +`Vec` *are two different types*, which are recognized as distinct by all +parts of the type system. + +This is called **monomorphization**, where different types are created from +**polymorphic** code. This special behavior requires `impl` blocks to specify +generic parameters: different values for the generic type cause different types, +and different types can have different `impl` blocks. + +In object oriented languages, classes can inherit behavior from their parents. +However, this allows the attachment of not only additional behavior to +particular members of a type class, but extra behavior as well. + +The nearest equivalent is the runtime polymorphism in Javascript and Python, +where new members can be added to objects willy-nilly by any constructor. +Unlike those languages, however, all of Rust's additional methods can be type +checked when they are used, because their generics are statically defined. That +makes them more usable while remaining safe. + +An example follows. + +## Example + +Suppose you are designing a storage server for a series of lab machines. +Because of the software involved, there are two different protocols you need +to support: BOOTP (for PXE network boot), and NFS (for remote mount storage). + +Your goal is to have one program, written in Rust, which can handle both of +them. It will have protocol handlers and listen for both kinds of requests. The +main application logic will then allow a lab administrator to configure storage +and security controls for the actual files. + +The requests from machines in the lab for files contain the same basic +information, no matter what protocol they came from: an authentication method, +and a file name to retrieve. A straightforward implementation would look +something like this: + +```rust,ignore +struct FileDownloadRequest { + file_name: PathBuf, + authentication: Either, +} +``` + +This design might work well enough. But now suppose you needed to support +adding metadata that was *protocol specific*. For example, with NFS, you +wanted to determine what their mount point was in order to enforce additional +security rules. + +The way the current struct is designed leaves the protocol decision until +runtime. That means any method that applies to one protocol and not the other +requires the programmer to do a runtime check. + +Here is how getting an NFS mount point would look: + +```rust,ignore +struct FileDownloadRequest { + file_name: PathBuf, + authentication: Either, + mount_point: Option, +} + +impl FileDownloadRequest { + // ... other methods ... + + /// Gets an NFS mount point if this is an NFS request. Otherwise, + /// return None. + pub fn mount_point(&self) -> Option<&Path> { + self.mount_point.as_ref() + } +} +``` + +Every caller of `get_mount point` must check for `None` and write code to handle +it. This is true even if they know only NFS requests are ever used in a given +code path! + +It would be far more optimal to cause a compile-time error if the different +request types were confused. After all, the entire path of the user's code, +including what functions from the library they use, will know whether a request +is an NFS request or a BOOTP request. + +In Rust, this is actually possible! The solution is to *add a generic type* in +order to split the API. + +Here is what that looks like: + +```rust +use std::path::{Path, PathBuf}; + +mod nfs { + #[derive(Clone)] + pub(crate) struct AuthInfo(String); // NFS session management omitted +} + +mod bootp { + pub(crate) struct AuthInfo(); // no authentication in bootp +} + +// private module, lest outside users invent their own protocol kinds! +mod proto_trait { + use std::path::{Path, PathBuf}; + use super::{bootp, nfs}; + pub(crate) trait ProtoKind { + type AuthInfo; + fn auth_info(&self) -> Self::AuthInfo; + } + pub struct Nfs(nfs::AuthInfo, PathBuf); // the mount point metadata + impl Nfs { + pub(crate) fn mount_point(&self) -> &Path { + &self.1 + } + } + impl ProtoKind for Nfs { + type AuthInfo = nfs::AuthInfo; + fn auth_info(&self) -> Self::AuthInfo { + self.0.clone() + } + } + pub struct Bootp(); // no additional metadata + impl ProtoKind for Bootp { + type AuthInfo = bootp::AuthInfo; + fn auth_info(&self) -> Self::AuthInfo { + bootp::AuthInfo() + } + } +} + +use proto_trait::ProtoKind; // keep internal to prevent impls +pub use proto_trait::{Nfs, Bootp}; // re-export so callers can see them + +struct FileDownloadRequest { + file_name: PathBuf, + protocol: P, +} + +// all common API parts go into a generic impl block +impl FileDownloadRequest

{ + fn file_path(&self) -> &Path { + &self.file_name + } + + fn auth_info(&self) -> P::AuthInfo { + self.protocol.auth_info() + } +} + +// all protocol-specific impls go into their own block +impl FileDownloadRequest { + fn mount_point(&self) -> &Path { + self.protocol.mount_point() + } +} +``` + +With this approach, if the user were to make a mistake and use the wrong +type; + +```rust,ignore +fn main() { + let mut socket = crate::bootp::listen()?; + while let Some(request) = socket.next_request()? { + match request.mount_point().as_ref() + "/secure" => socket.send("Access denied"), + _ => {} // continue on... + } + // Rest of the code here + } +} +``` + +They would get a syntax error. The type `FileDownloadRequest` does not +implement `mount_point()`, only the type `FileDownloadRequest` does. And +that is created by the NFS module, not the BOOTP module of course! + +## Advantages + +First, it allows fields that are common to multiple states to be de-duplicated. +By making the non-shared fields generic, they are implemented once. + +Second, it makes the `impl` blocks easier to read, because they are broken down +by state. Methods common to all states are typed once in one block, and methods +unique to one state are in a separate block. + +Both of these mean there is less code and it is better organized. + +## Disadvantages + +This increases code size. Hopefully the compiler will be more intelligent about +the way monomorphization is implemented in the future. + +## Alternatives + +If a type seems to need a "split API" due to construction or partial +initialization, consider the +[Builder Pattern](../patterns/creational/builder.md) instead. + +If the API between types does not change -- only the behavior does -- then the +[Strategy Pattern](../patterns/behavioural/strategy.md) is better used instead. + +## See also + +This pattern is used throughout the standard library: + +* `Vec` can be cast from a String, unlike every other type of `Vec`.[^1] +* They can also be cast into a binary heap, but only if they contain a type + that implements the `Ord` trait.[^2] +* The `to_string` method was specialized for `Cow` only of type `str`.[^3] + +It is also used by several popular crates to allow API flexibility: + +* The `embedded-hal` ecosystem used for embedded devices makes extensive use of + this pattern. For example, it allows statically verifying the configuration of + device registers used to control embedded pins. When a pin is put into a mode, + it returns a `Pin` struct, whose generic determines the functions + usable in that mode, which are not on the `Pin` itself. [^4] + +* The `hyper` HTTP client library uses this to expose rich APIs for different + pluggable requests. Clients with different connectors have different methods + on them as well as different trait implementations, while a core set of + methods apply to any connector. [^5] + +* The "type state" pattern -- where an object gains and loses API based on an + internal state or invariant -- is implemented in Rust using the same basic + concept, and a slightly different techinque. [^6] + +[^1] See: [impl From\ for Vec\]( +https://doc.rust-lang.org/stable/src/std/ffi/c_str.rs.html#799-801) + + +[^2] See: [impl\ From\\> for BinaryHeap\]( +https://doc.rust-lang.org/stable/src/alloc/collections/binary_heap.rs.html#1345-1354) + +[^3] See: [impl\<'_\> ToString for Cow\<'_, str>]( +https://doc.rust-lang.org/stable/src/alloc/string.rs.html#2235-2240) + +[^4] Example: +[https://docs.rs/stm32f30x-hal/0.1.0/stm32f30x_hal/gpio/gpioa/struct.PA0.html]( +https://docs.rs/stm32f30x-hal/0.1.0/stm32f30x_hal/gpio/gpioa/struct.PA0.html) + +[^5] See: +[https://docs.rs/hyper/0.14.5/hyper/client/struct.Client.html]( +https://docs.rs/hyper/0.14.5/hyper/client/struct.Client.html) + +[^6] See: +[The Case for the Type State Pattern]( +https://web.archive.org/web/20210325065112/https://www.novatec-gmbh.de/en/blog/the-case-for-the-typestate-pattern-the-typestate-pattern-itself/) +and +[Rusty Typestate Series (an extensive thesis)]( +https://web.archive.org/web/20210328164854/https://rustype.github.io/notes/notes/rust-typestate-series/rust-typestate-index) From fe990c4ed96d02ae70a7535e61071314f3f0a4ad Mon Sep 17 00:00:00 2001 From: jhwgh1968 Date: Sun, 11 Apr 2021 11:03:48 -0500 Subject: [PATCH 02/13] Fix some review comments --- functional/generics-type-classes.md | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/functional/generics-type-classes.md b/functional/generics-type-classes.md index 2df98f01..01b2d634 100644 --- a/functional/generics-type-classes.md +++ b/functional/generics-type-classes.md @@ -10,15 +10,15 @@ of Rust's compile time guarantees. A key part of this idea is the way generic types work. In C++ and Java, for example, generic types are a meta-programming construct for the compiler. -`Vec` and `Vec` in C++ are just two different copies of the same -boilerplate code for a `Vec` type (known as a `template`) with two +`vector` and `vector` in C++ are just two different copies of the +same boilerplate code for a `Vec` type (known as a `template`) with two different types filled in. -In Rust, a generic type parameter creates what is known in functional -languages as a "type class constraint", and each different parameter filled in -by an end user *actually changes the type*. In other words, `Vec` and -`Vec` *are two different types*, which are recognized as distinct by all -parts of the type system. +In Rust, a generic type parameter creates what is known in functional languages +as a "type class constraint", and each different parameter filled in by an end +user *actually changes the type*. In other words, `Vec` and `Vec` +*are two different types*, which are recognized as distinct by all parts of the +type system. This is called **monomorphization**, where different types are created from **polymorphic** code. This special behavior requires `impl` blocks to specify @@ -35,8 +35,6 @@ Unlike those languages, however, all of Rust's additional methods can be type checked when they are used, because their generics are statically defined. That makes them more usable while remaining safe. -An example follows. - ## Example Suppose you are designing a storage server for a series of lab machines. @@ -54,9 +52,15 @@ and a file name to retrieve. A straightforward implementation would look something like this: ```rust,ignore + +enum AuthInfo { + Nfs(crate::nfs::AuthInfo), + Bootp(crate::bootp::AuthInfo), +} + struct FileDownloadRequest { file_name: PathBuf, - authentication: Either, + authentication: AuthInfo, } ``` @@ -74,7 +78,7 @@ Here is how getting an NFS mount point would look: ```rust,ignore struct FileDownloadRequest { file_name: PathBuf, - authentication: Either, + authentication: AuthInfo, mount_point: Option, } @@ -89,7 +93,7 @@ impl FileDownloadRequest { } ``` -Every caller of `get_mount point` must check for `None` and write code to handle +Every caller of `mount point()` must check for `None` and write code to handle it. This is true even if they know only NFS requests are ever used in a given code path! From b77406a639df5dbbdfc50a2b4d2e074c52d8f977 Mon Sep 17 00:00:00 2001 From: jhwgh1968 Date: Sun, 11 Apr 2021 16:05:13 +0000 Subject: [PATCH 03/13] Fix footnotes Co-authored-by: Marco Ieni <11428655+MarcoIeni@users.noreply.github.com> --- functional/generics-type-classes.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/functional/generics-type-classes.md b/functional/generics-type-classes.md index 01b2d634..ab33584a 100644 --- a/functional/generics-type-classes.md +++ b/functional/generics-type-classes.md @@ -246,25 +246,25 @@ It is also used by several popular crates to allow API flexibility: internal state or invariant -- is implemented in Rust using the same basic concept, and a slightly different techinque. [^6] -[^1] See: [impl From\ for Vec\]( +[^1]: See: [impl From\ for Vec\]( https://doc.rust-lang.org/stable/src/std/ffi/c_str.rs.html#799-801) -[^2] See: [impl\ From\\> for BinaryHeap\]( +[^2]: See: [impl\ From\\> for BinaryHeap\]( https://doc.rust-lang.org/stable/src/alloc/collections/binary_heap.rs.html#1345-1354) -[^3] See: [impl\<'_\> ToString for Cow\<'_, str>]( +[^3]: See: [impl\<'_\> ToString for Cow\<'_, str>]( https://doc.rust-lang.org/stable/src/alloc/string.rs.html#2235-2240) -[^4] Example: +[^4]: Example: [https://docs.rs/stm32f30x-hal/0.1.0/stm32f30x_hal/gpio/gpioa/struct.PA0.html]( https://docs.rs/stm32f30x-hal/0.1.0/stm32f30x_hal/gpio/gpioa/struct.PA0.html) -[^5] See: +[^5]: See: [https://docs.rs/hyper/0.14.5/hyper/client/struct.Client.html]( https://docs.rs/hyper/0.14.5/hyper/client/struct.Client.html) -[^6] See: +[^6]: See: [The Case for the Type State Pattern]( https://web.archive.org/web/20210325065112/https://www.novatec-gmbh.de/en/blog/the-case-for-the-typestate-pattern-the-typestate-pattern-itself/) and From 26932d10385d2aef5b6178fcbba4f751072704f8 Mon Sep 17 00:00:00 2001 From: jhwgh1968 Date: Sun, 11 Apr 2021 11:07:15 -0500 Subject: [PATCH 04/13] Fix hard tabs --- functional/generics-type-classes.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/functional/generics-type-classes.md b/functional/generics-type-classes.md index ab33584a..b654e21e 100644 --- a/functional/generics-type-classes.md +++ b/functional/generics-type-classes.md @@ -54,8 +54,8 @@ something like this: ```rust,ignore enum AuthInfo { - Nfs(crate::nfs::AuthInfo), - Bootp(crate::bootp::AuthInfo), + Nfs(crate::nfs::AuthInfo), + Bootp(crate::bootp::AuthInfo), } struct FileDownloadRequest { From abdf5a6e41ec68f7e19b58318d9648869e32e5cd Mon Sep 17 00:00:00 2001 From: jhwgh1968 Date: Sun, 11 Apr 2021 11:07:29 -0500 Subject: [PATCH 05/13] Remove extra blank line --- functional/generics-type-classes.md | 1 - 1 file changed, 1 deletion(-) diff --git a/functional/generics-type-classes.md b/functional/generics-type-classes.md index b654e21e..69800766 100644 --- a/functional/generics-type-classes.md +++ b/functional/generics-type-classes.md @@ -249,7 +249,6 @@ It is also used by several popular crates to allow API flexibility: [^1]: See: [impl From\ for Vec\]( https://doc.rust-lang.org/stable/src/std/ffi/c_str.rs.html#799-801) - [^2]: See: [impl\ From\\> for BinaryHeap\]( https://doc.rust-lang.org/stable/src/alloc/collections/binary_heap.rs.html#1345-1354) From f7f57fc93d888f23ac9c54e339badab66ff25315 Mon Sep 17 00:00:00 2001 From: jhwgh1968 Date: Sun, 11 Apr 2021 11:10:26 -0500 Subject: [PATCH 06/13] Fix some review comments --- functional/generics-type-classes.md | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/functional/generics-type-classes.md b/functional/generics-type-classes.md index 69800766..0dc080f1 100644 --- a/functional/generics-type-classes.md +++ b/functional/generics-type-classes.md @@ -204,21 +204,23 @@ Second, it makes the `impl` blocks easier to read, because they are broken down by state. Methods common to all states are typed once in one block, and methods unique to one state are in a separate block. -Both of these mean there is less code and it is better organized. +Both of these mean there are fewer lines of code and it is better organized. ## Disadvantages -This increases code size. Hopefully the compiler will be more intelligent about -the way monomorphization is implemented in the future. +This current increases the size of the binary, due to the way monomorphization +is implemented in the compiler. Hopefully the implementation will be able to +improve in the future. ## Alternatives -If a type seems to need a "split API" due to construction or partial +* If a type seems to need a "split API" due to construction or partial initialization, consider the [Builder Pattern](../patterns/creational/builder.md) instead. -If the API between types does not change -- only the behavior does -- then the -[Strategy Pattern](../patterns/behavioural/strategy.md) is better used instead. +* If the API between types does not change -- only the behavior does -- then +the [Strategy Pattern](../patterns/behavioural/strategy.md) is better used +instead. ## See also From f0f2aa35b8eba03533b4fb4789d53cc9a3eb50cb Mon Sep 17 00:00:00 2001 From: jhwgh1968 Date: Sun, 11 Apr 2021 11:18:57 -0500 Subject: [PATCH 07/13] Grammar fix --- functional/generics-type-classes.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/functional/generics-type-classes.md b/functional/generics-type-classes.md index 0dc080f1..09b08bba 100644 --- a/functional/generics-type-classes.md +++ b/functional/generics-type-classes.md @@ -204,7 +204,7 @@ Second, it makes the `impl` blocks easier to read, because they are broken down by state. Methods common to all states are typed once in one block, and methods unique to one state are in a separate block. -Both of these mean there are fewer lines of code and it is better organized. +Both of these mean there are fewer lines of code, and they are better organized. ## Disadvantages From e2845c6f66317b89284646c1dd5d5592b2be2373 Mon Sep 17 00:00:00 2001 From: jhwgh1968 Date: Sun, 11 Apr 2021 11:22:13 -0500 Subject: [PATCH 08/13] Expand Nfs into typed struct --- functional/generics-type-classes.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/functional/generics-type-classes.md b/functional/generics-type-classes.md index 09b08bba..150e2c72 100644 --- a/functional/generics-type-classes.md +++ b/functional/generics-type-classes.md @@ -127,16 +127,19 @@ mod proto_trait { type AuthInfo; fn auth_info(&self) -> Self::AuthInfo; } - pub struct Nfs(nfs::AuthInfo, PathBuf); // the mount point metadata + pub struct Nfs { + auth: nfs::AuthInfo, + mount_point: PathBuf, + } impl Nfs { pub(crate) fn mount_point(&self) -> &Path { - &self.1 + &self.mount_point } } impl ProtoKind for Nfs { type AuthInfo = nfs::AuthInfo; fn auth_info(&self) -> Self::AuthInfo { - self.0.clone() + self.auth.clone() } } pub struct Bootp(); // no additional metadata From 59129ca80dd349e3660926e8ace52871c378141d Mon Sep 17 00:00:00 2001 From: jhwgh1968 Date: Sun, 11 Apr 2021 11:22:40 -0500 Subject: [PATCH 09/13] Add empty main to satisfy mdbook --- functional/generics-type-classes.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/functional/generics-type-classes.md b/functional/generics-type-classes.md index 150e2c72..f312d852 100644 --- a/functional/generics-type-classes.md +++ b/functional/generics-type-classes.md @@ -176,6 +176,10 @@ impl FileDownloadRequest { self.protocol.mount_point() } } + +fn main() { + // your code here +} ``` With this approach, if the user were to make a mistake and use the wrong From 6a0717e401ee38c0de4ebdc3380f9f9238519961 Mon Sep 17 00:00:00 2001 From: jhwgh1968 Date: Sun, 11 Apr 2021 11:23:06 -0500 Subject: [PATCH 10/13] Add blanks for readability --- functional/generics-type-classes.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/functional/generics-type-classes.md b/functional/generics-type-classes.md index f312d852..5cab5d31 100644 --- a/functional/generics-type-classes.md +++ b/functional/generics-type-classes.md @@ -123,26 +123,32 @@ mod bootp { mod proto_trait { use std::path::{Path, PathBuf}; use super::{bootp, nfs}; + pub(crate) trait ProtoKind { type AuthInfo; fn auth_info(&self) -> Self::AuthInfo; } + pub struct Nfs { auth: nfs::AuthInfo, mount_point: PathBuf, } + impl Nfs { pub(crate) fn mount_point(&self) -> &Path { &self.mount_point } } + impl ProtoKind for Nfs { type AuthInfo = nfs::AuthInfo; fn auth_info(&self) -> Self::AuthInfo { self.auth.clone() } } + pub struct Bootp(); // no additional metadata + impl ProtoKind for Bootp { type AuthInfo = bootp::AuthInfo; fn auth_info(&self) -> Self::AuthInfo { From 6a51c699b26770911295f59cd5632010070a3e04 Mon Sep 17 00:00:00 2001 From: jhwgh1968 Date: Sun, 11 Apr 2021 19:29:00 +0000 Subject: [PATCH 11/13] Typo fix Co-authored-by: Marco Ieni <11428655+MarcoIeni@users.noreply.github.com> --- functional/generics-type-classes.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/functional/generics-type-classes.md b/functional/generics-type-classes.md index 5cab5d31..743bbf96 100644 --- a/functional/generics-type-classes.md +++ b/functional/generics-type-classes.md @@ -93,7 +93,7 @@ impl FileDownloadRequest { } ``` -Every caller of `mount point()` must check for `None` and write code to handle +Every caller of `mount_point()` must check for `None` and write code to handle it. This is true even if they know only NFS requests are ever used in a given code path! From cd0150caf03d67ce5eaaa75e500ea580bd13045e Mon Sep 17 00:00:00 2001 From: jhwgh1968 Date: Sun, 11 Apr 2021 19:29:50 +0000 Subject: [PATCH 12/13] Typo fix Co-authored-by: Marco Ieni <11428655+MarcoIeni@users.noreply.github.com> --- functional/generics-type-classes.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/functional/generics-type-classes.md b/functional/generics-type-classes.md index 743bbf96..23f1eb2e 100644 --- a/functional/generics-type-classes.md +++ b/functional/generics-type-classes.md @@ -11,7 +11,7 @@ of Rust's compile time guarantees. A key part of this idea is the way generic types work. In C++ and Java, for example, generic types are a meta-programming construct for the compiler. `vector` and `vector` in C++ are just two different copies of the -same boilerplate code for a `Vec` type (known as a `template`) with two +same boilerplate code for a `vector` type (known as a `template`) with two different types filled in. In Rust, a generic type parameter creates what is known in functional languages From 86bb005e65a3b3476c87cd0ee88a43f45860dc9a Mon Sep 17 00:00:00 2001 From: simonsan <14062932+simonsan@users.noreply.github.com> Date: Fri, 16 Apr 2021 11:13:15 +0200 Subject: [PATCH 13/13] Update functional/generics-type-classes.md --- functional/generics-type-classes.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/functional/generics-type-classes.md b/functional/generics-type-classes.md index 23f1eb2e..9999e921 100644 --- a/functional/generics-type-classes.md +++ b/functional/generics-type-classes.md @@ -221,7 +221,7 @@ Both of these mean there are fewer lines of code, and they are better organized. ## Disadvantages -This current increases the size of the binary, due to the way monomorphization +This currently increases the size of the binary, due to the way monomorphization is implemented in the compiler. Hopefully the implementation will be able to improve in the future.