From e09ee6855b55e3af7635c535ccfaf9a1feffece6 Mon Sep 17 00:00:00 2001 From: Ozgun Ozerk Date: Sun, 29 Sep 2024 18:09:34 +0300 Subject: [PATCH 1/2] follow the suggestion from reddit --- README.md | 47 ++++++++++++++++++++++++++++++++++-- examples/complex_expanded.rs | 8 +++--- examples/simple_expanded.rs | 4 +-- src/lib.rs | 4 ++- tests/complex_example.rs | 8 +++--- tests/simple_example.rs | 2 +- 6 files changed, 59 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index b3d1658..b9fd312 100644 --- a/README.md +++ b/README.md @@ -2,9 +2,25 @@ # What is State-Shift? -**State-Shift** is a macro library that generates **type-state-pattern boilerplate code for your structs**. So that you can write your structs as usual, but still get the benefits of type-safety! +I love type-state pattern's promises: -If you don't appreciate all the boilerplate code required by Type-State-Pattern that makes the DevX worse, but you still like the idea of type-safety provided by it, this library is for you. Type-State-Pattern-Macro lets you write your code as if type-state-pattern was not there, yet grants you the benefits of type-safety. BEST OF BOTH WORLDS! +- compile time checks + +- better/safer auto completion suggestions by your IDE + +- no additional runtime costs + +However, I agree that in order to utilize type-state pattern, the code has to become quite ugly. We are talking about less readable and maintainable code, just because of this. + +Although I'm a fan, I agree usually it's not a good idea to use type-state pattern. + +And THAT, my friends, bothered me... + +So I wrote `state-shift`. + +TL;DR -> it lets you convert your structs and methods into type-state version, without the ugly code. So, best of both worlds! + +If you don't appreciate all the boilerplate code required by Type-State-Pattern that makes the DevX worse, but you still like the idea of type-safety provided by it, this library is for you. `state-shift` lets you write your code as if type-state-pattern was not there, yet grants you the benefits of type-safety. ### What the hell is even Type-State-Pattern? @@ -477,4 +493,31 @@ struct PlayerBuilder { } ``` +2. How do I pass the player to a function (no method), does it require extra type annotations to specify the state? + +Say you have this: + +```rust +fn player_builder_logger(player_builder: PlayerBuilder) { + println!("PlayerBuilder's level: {:?}", player_builder.level); +} +``` + +You can pass the `player_builder` without any type-annotation, but then it would expect the states to be equal to the default ones, in this case: `PlayerBuilder`. + +If you want to pass another state, I think you have to explicitly tell the code: +```rust +fn player_builder_logger(player_builder: PlayerBuilder) { + println!("PlayerBuilder's level: {:?}", player_builder.level); +} +``` + +Then you can call it like this: +```rust +fn main() { + let player = PlayerBuilder::new().set_race(Race::Human).set_level(4); + player_builder_logger(player); +} +``` + Happy coding! diff --git a/examples/complex_expanded.rs b/examples/complex_expanded.rs index 2e0ad5a..19dd8c7 100644 --- a/examples/complex_expanded.rs +++ b/examples/complex_expanded.rs @@ -30,9 +30,9 @@ where skill_slots: Option, spell_slots: Option, _state: ( - PhantomData, - PhantomData, - PhantomData, + PhantomData State1>, + PhantomData State2>, + PhantomData State3>, ), } @@ -61,7 +61,7 @@ impl TypeStateProtector for SkillSlotsSet {} impl TypeStateProtector for SpellSlotsSet {} // put the constructors in a separate impl block -impl PlayerBuilder { +impl PlayerBuilder { fn new() -> Self { PlayerBuilder { race: None, diff --git a/examples/simple_expanded.rs b/examples/simple_expanded.rs index 0ec0365..d72ebcb 100644 --- a/examples/simple_expanded.rs +++ b/examples/simple_expanded.rs @@ -23,7 +23,7 @@ struct PlayerBuilder { level: Option, skill_slots: Option, #[allow(unused_parens)] - _state: (PhantomData), + _state: PhantomData State1>, } mod sealed { @@ -48,7 +48,7 @@ impl TypeStateProtector for LevelSet {} impl TypeStateProtector for SkillSlotsSet {} // put the constructors in a separate impl block -impl PlayerBuilder { +impl PlayerBuilder { fn new() -> Self { PlayerBuilder { race: None, diff --git a/src/lib.rs b/src/lib.rs index 90607ea..0f3f8de 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -382,9 +382,11 @@ pub fn type_state(args: TokenStream, input: TokenStream) -> TokenStream { }); // Construct the `_state` field with PhantomData + // `_state: PhantomData T>` + // the reason for using `fn() -> T` is to: https://github.com/ozgunozerk/state-shift/issues/1 let phantom_fields = state_idents .iter() - .map(|ident| quote!(PhantomData<#ident>)) + .map(|ident| quote!(PhantomData #ident>)) .collect::>(); let output = quote! { diff --git a/tests/complex_example.rs b/tests/complex_example.rs index d752a72..75286c6 100644 --- a/tests/complex_example.rs +++ b/tests/complex_example.rs @@ -26,20 +26,20 @@ struct PlayerBuilder { } // put the constructors in a separate impl block +impl PlayerBuilder {} + +#[states(Initial, RaceSet, LevelSet, SkillSlotsSet, SpellSlotsSet)] impl PlayerBuilder { + #[require(Initial, Initial, Initial)] // require the default state for the constructor fn new() -> Self { PlayerBuilder { race: None, level: None, skill_slots: None, spell_slots: None, - _state: (PhantomData, PhantomData, PhantomData), } } -} -#[states(Initial, RaceSet, LevelSet, SkillSlotsSet, SpellSlotsSet)] -impl PlayerBuilder { #[require(Initial, B, C)] // can be called only at `Initial` state. #[switch_to(RaceSet, B, C)] // Transitions to `RaceSet` state fn set_race(self, race: Race) -> PlayerBuilder { diff --git a/tests/simple_example.rs b/tests/simple_example.rs index ed8ac2d..d35d415 100644 --- a/tests/simple_example.rs +++ b/tests/simple_example.rs @@ -25,7 +25,7 @@ struct PlayerBuilder { #[states(Initial, RaceSet, LevelSet, SkillSlotsSet)] impl PlayerBuilder { - #[require(Initial)] + #[require(Initial)] // require the default state for the constructor fn new() -> PlayerBuilder { PlayerBuilder { race: None, From 602efd9d49ac9770883c2b91b01807715d53d62f Mon Sep 17 00:00:00 2001 From: Ozgun Ozerk Date: Sun, 29 Sep 2024 18:16:35 +0300 Subject: [PATCH 2/2] clippy warning fix --- examples/complex_expanded.rs | 1 + src/lib.rs | 1 + 2 files changed, 2 insertions(+) diff --git a/examples/complex_expanded.rs b/examples/complex_expanded.rs index 19dd8c7..a1cadb5 100644 --- a/examples/complex_expanded.rs +++ b/examples/complex_expanded.rs @@ -19,6 +19,7 @@ enum Race { Human, } +#[allow(clippy::type_complexity)] struct PlayerBuilder where State1: TypeStateProtector, diff --git a/src/lib.rs b/src/lib.rs index 0f3f8de..7af0594 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -390,6 +390,7 @@ pub fn type_state(args: TokenStream, input: TokenStream) -> TokenStream { .collect::>(); let output = quote! { + #[allow(clippy::type_complexity)] struct #struct_name<#(#state_idents = #default_generics),*> where #(#where_clauses),*