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

follow the suggestion from reddit #2

Merged
merged 2 commits into from
Sep 29, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
47 changes: 45 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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?
Expand Down Expand Up @@ -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<Initial>`.

If you want to pass another state, I think you have to explicitly tell the code:
```rust
fn player_builder_logger(player_builder: PlayerBuilder<LevelSet>) {
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!
9 changes: 5 additions & 4 deletions examples/complex_expanded.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ enum Race {
Human,
}

#[allow(clippy::type_complexity)]
struct PlayerBuilder<State1 = Initial, State2 = Initial, State3 = Initial>
where
State1: TypeStateProtector,
Expand All @@ -30,9 +31,9 @@ where
skill_slots: Option<u8>,
spell_slots: Option<u8>,
_state: (
PhantomData<State1>,
PhantomData<State2>,
PhantomData<State3>,
PhantomData<fn() -> State1>,
PhantomData<fn() -> State2>,
PhantomData<fn() -> State3>,
),
}

Expand Down Expand Up @@ -61,7 +62,7 @@ impl TypeStateProtector for SkillSlotsSet {}
impl TypeStateProtector for SpellSlotsSet {}

// put the constructors in a separate impl block
impl PlayerBuilder {
impl PlayerBuilder<Initial, Initial, Initial> {
fn new() -> Self {
PlayerBuilder {
race: None,
Expand Down
4 changes: 2 additions & 2 deletions examples/simple_expanded.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ struct PlayerBuilder<State1 = Initial> {
level: Option<u8>,
skill_slots: Option<u8>,
#[allow(unused_parens)]
_state: (PhantomData<State1>),
_state: PhantomData<fn() -> State1>,
}

mod sealed {
Expand All @@ -48,7 +48,7 @@ impl TypeStateProtector for LevelSet {}
impl TypeStateProtector for SkillSlotsSet {}

// put the constructors in a separate impl block
impl PlayerBuilder {
impl PlayerBuilder<Initial> {
fn new() -> Self {
PlayerBuilder {
race: None,
Expand Down
5 changes: 4 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -382,12 +382,15 @@ pub fn type_state(args: TokenStream, input: TokenStream) -> TokenStream {
});

// Construct the `_state` field with PhantomData
// `_state: PhantomData<fn() -> 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<fn() -> #ident>))
.collect::<Vec<_>>();

let output = quote! {
#[allow(clippy::type_complexity)]
struct #struct_name<#(#state_idents = #default_generics),*>
where
#(#where_clauses),*
Expand Down
8 changes: 4 additions & 4 deletions tests/complex_example.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
2 changes: 1 addition & 1 deletion tests/simple_example.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down