To those wondering why not use a simple enum, yes, in this simple case, the whole setup is a bit overkill. But imagine a complex machine with lots of sensors, motors, etc...in such a case it would be more maintainable if you have a 'self-aware' state that 'knows' what it can and cannot do, rather than have lots of nested "if(state=something) then/else..." spread all over your code. E.g. if you have a Sleep state, a programmer would find it much easier to add extra restrictions to what tasks can still happen in the Sleep state if all that stuff is grouped in one SleepState definition.
For those saying "just use an enum", every time you use the post you'd have to check (match) the state before using its methods. The type-enforced state is much better because Rust is aware of an instance type, so the state is already known at every point in time.
9:04 To avoid the unnecessary wrapping in `Option`, you could use `mem::replace` instead of `Option.take`, which gets rid of the need to have the intermediate None value.
How is it I can trawl through docs for days trying to understand the ' annotations and still be lost, but you just brush over it in 20 seconds and I suddenly get it? Awesome job man, thanks for that
I feel this chapter was sort of a weird flex from the book authors: "Look, we can implement OOP patterns in Rust if we really wanted to. Although our language features make it kinda ugly. But those same features allow us to make much nicer implementations that take advantage of compile time error safeguards."
I guess is a way to show that taking advantage of the features of the language is better than trying to force the use of a OOP pattern, which could be considered over engineering the problem.
That's one way to do it but I find it heavy. I'd rather use the interesting null cost of an empty struct marker type pattern, and define 'struct Post', empty structs for 'Draft', 'PendingReview' and so on. Each of those struct with the methods in their implementation, instead of fake ones like here. This requires to reassign 'post', but is much lighter and better isolates the specific methods. I suppose it depends if there are many common methods to all the states, which may take more code space when defined in the generic implementation 'impl Post { ... }'.
Why not just use a State variable of type enum? This would make it possible to create a much simpler and vastly clearer program. The mapping between the problem and solution should be as direct and clear as possible. The structure of the solution should mimic that of the problem. Its also easier to start with a state diagram, defining each state with arcs between them representing each valid state transition. Great for team and user discussions. Make it as simple as possible, but useful
correct me if I'm wrong but I think the downside in this example of using an enum is that you can't implement a trait for an enum variant specifically, so you would have to implement methods differently, probably with a Result type as output
@@Roms8313 Yes I agree. I think the object oriented version makes it easier to dynamically alter the ‘states’ by adding or removing ‘state objects’ . Whereas if an enum is used to define the set of states it can become more static in nature. But of course there may be mechanisms to extend set of enumeration values 🙂, which would have the benefit of making the evolution of the program more evident. My preference just happens to encourage the simplicity and visual clarity of the program as a whole throughout its lifetime of use and adaption to user and platform needs as they evolve. I always try to have compassion for the unfortunate maintainer who some time in the future has to review the implementation, understand it and its implications (performance, limits, testability …) and the add in support for correcting implementation errors and/or new features.
I was about to make this comment. The approach Bogdan used is very OOP-esque and not particularly idiomatic in my opinion. Personally I would much prefer to use a non-exhaustive enum wrapped in a Post struct in this particular case, and put all the business logic within methods of Post. This has three main advantages: 1. Less code duplication and boilerplate code 2. Static dispatch, therefore more efficient 3. The caller is better able to get feedback on the status of each transaction, if those methods return a Result type. (Although I suppose that's possible too in Bogdan's approach)
I personally think that the state pattern is pretty ugly, specially if we need to deal with Rust details using things like Options and Boxes, but it does provide some maintainability advantages. Imagine we would add more states. With the enum approach, we would have a bunch of match expressions in all of our methods, and so we would need to go through each and every one of these methods and add arms correponding to the new states. Using the state pattern approach instead, we would only need to implement methods in the new states and possibly modify some other states.
Excellent stuff. Thank you. I had had the opportunity to put to use principles of FSM in designing software for Credit-Card Auth Protocol, CDR processing in Telelcom, Online Multiplayer Gaming, Contests in Training by Gamification etc., using 'C', 'Java' and 'Scala'. I am learning Rust and this presentation guides me towards building similar software in Rust. Letting Types do that talking, in a manner of speaking, is not straightforward in my experience but once done, its adoption benefits are manifold.
Wow. Much needed video. I have been trying to learn these patterns for a while. This video explains state pattern very nicely and I understood more than I did by reading other materials. This video also shows rust having better approaches, which blew Mind blown. Thanks a lot. What do I search for to learn more on such 'rust patterns'?
Hey, man. You know what's going to be interesting... Talk about data-oriented in RUST. I'm doing research on this topic but is not easy to have examples...
Try checking for some ECS, it's a key concept of data oriented programming, although mainly used in game programming. The idea is to reduce cache misses by keeping every component in it's own list, then making systems that are basically some queries to get logic done. An entity is just a number, so it can be copied easily. If it's too hard to start with, try beginning by understanding what are generational arenas allocators. You can check Hecs for an ECS in rust. And there is some great talks by professionals on the fields about this. Hope it helps :)
Hmm. I've really been trying to make the similarish builder-pattern work, but I have to admit I never found a situation where I did not eventually conclude that other abstractions proved superior. It's just that you end up with a bunch of different initialization states that make predictable behavior difficult, and you end up with a lot of methods that are meaningless after being called the first time. Edit: Yeah, the encoded in Type variant is more like what I've concluded is to be done if I really need it.
@@Nuclear868 Well, probably yeah. The other pattern might just do the same though, and also thb copying is usually cheap compared to heap allocations. And the way at least Rusty implemented it the original pattern causes a lot of allocations, where the updated one causes none at all, because it doesn't use Box. At least IIRC.
@@Nuclear868 Well, revisiting it, what I said is the case. Excluding string allocations that happen in any case. Although, also, I'm not sure how Rust treats these zero size struct in boxes, it might optimize them away. TBH I would not think about optimizations when any of these patterns are regarded. They're made to be easy to use, not to perform well, and their usage does not tend to end up in hot loops, if they do you should rethink.
The second approach switching types is looks pretty robust. For code where I want really strict safety type checks during compile time, this might be way to go.
As you could see in the video, there are many ways to get the same outcome. The state design pattern provided encapsulation, that you would lose if you just used an enum. Meaning, if every function has a huge switch statement, all the logic of every state is inside that function, instead of being distributed in each state. If states are complex, separating/encapsulating things may be desirable. Also, the second aproach is also another way of solving the same situation, using the type system. There are always many ways to do the same, so ideally you should know a bunch and waver their pros and cons to use the one that better suits your needs.
As always, GREAT content. Really gave allot of great concepts that I have been working with myself during my development of my task manager. A few notes, while I know you end up changing the implementation in the second part, the state field gave me major "maybe not?" vibes: th-cam.com/video/YR5WdGrpoug/w-d-xo.html. as a general rule, I try to avoid maybe types like Option within the actual struct body (see video for reasons why). Your second example was probably more idiomatic, but I think a small change you could make is giving a reference to self, rather than having the function consume self (or even the mutable reference for that matter) and simply return a NEW variant of the object with the required changes. This ends up being MUCH less finickey with ownership since you no longer need to think about the ownership change flow when putting this into a larger program, these methods can be chained or done atomically as one could see fit. And while the idea of the old version being consumed seems more elegant, there are often many good reasons to keep the former version of the object around just in case you need to use it some more (a great example is a comparison function where you tell the user that you converted an object from one state to another or something). Finally, if we end up not needing the old object after we get the new one, it will simply be cleaned up at the end of the function due to rust's dropping mechanism. While a die-hard performance buff would argue that creating a new object is inefficient, I have found that there is no real performance loss in day-to-day use unless you are REALLY resource constrained, and in exchange you get a functional layout that makes reasoning about an object's ownership status at any time trivially easy (even within an async workflow since there are almost no locks from an object holding a mutable reference hostage from the other threads). This was a GREAT showcase of how the object design pattern is implemented in rust and i'm excited for more content.
(To anyone) Would there be any advantage to put all the state types into the fields an enem ? You could use match on the variant to choose which method to run and all enum variants would have to be covered or you would get an error.
By the way, why do we need to provide our email for the cheat sheet, what are you doing with that information? "I have been fully informed and consent to the collection and use of my personal data **for any purpose in connection with the software, products and/or services**." seems pretty wide.
Because we are relying on each state to have that method, even if it does nothing, since we are calling those methods independently of the state the post is in.
hey is there any chance of you publishing these explaining videos' source code into github or something? I wanted to look deeper and play around with the code myself anyway, great videos! I hope your channel blow up very soon :)
Is it not possible to construct all these structs without calling new methods? That way we can bypass the whole state system an e.g. construct a Post right at the spot.
First example with unified interface quickly backfires - returning empty string as "special value" instead of Option is sketchy AF. The only advantage of this approach I see is that Box logic is hidden from library user. While there may be situation in second solution where user would have to use Box to encapsulate all different state types himself. BTW: I love this taking ownership of self trick. It makes second solution so bomb-proof just by pure language design. You cannot accidentally "fork" states and keep using old one and new one at the same time.
📝 Get your *FREE Rust cheat sheet* : www.letsgetrusty.com/cheatsheet
To those wondering why not use a simple enum, yes, in this simple case, the whole setup is a bit overkill. But imagine a complex machine with lots of sensors, motors, etc...in such a case it would be more maintainable if you have a 'self-aware' state that 'knows' what it can and cannot do, rather than have lots of nested "if(state=something) then/else..." spread all over your code. E.g. if you have a Sleep state, a programmer would find it much easier to add extra restrictions to what tasks can still happen in the Sleep state if all that stuff is grouped in one SleepState definition.
For those saying "just use an enum", every time you use the post you'd have to check (match) the state before using its methods.
The type-enforced state is much better because Rust is aware of an instance type, so the state is already known at every point in time.
9:04 To avoid the unnecessary wrapping in `Option`, you could use `mem::replace` instead of `Option.take`, which gets rid of the need to have the intermediate None value.
How is it I can trawl through docs for days trying to understand the ' annotations and still be lost, but you just brush over it in 20 seconds and I suddenly get it? Awesome job man, thanks for that
It’s called experience
I feel this chapter was sort of a weird flex from the book authors: "Look, we can implement OOP patterns in Rust if we really wanted to. Although our language features make it kinda ugly. But those same features allow us to make much nicer implementations that take advantage of compile time error safeguards."
I guess is a way to show that taking advantage of the features of the language is better than trying to force the use of a OOP pattern, which could be considered over engineering the problem.
That's one way to do it but I find it heavy. I'd rather use the interesting null cost of an empty struct marker type pattern, and define 'struct Post', empty structs for 'Draft', 'PendingReview' and so on. Each of those struct with the methods in their implementation, instead of fake ones like here. This requires to reassign 'post', but is much lighter and better isolates the specific methods. I suppose it depends if there are many common methods to all the states, which may take more code space when defined in the generic implementation 'impl Post { ... }'.
Thank you for this comment. I would not have thought to do this and it seems like a fantastic approach for a number of my projects
Why not just use a State variable of type enum?
This would make it possible to create a much simpler and vastly clearer program. The mapping between the problem and solution should be as direct and clear as possible. The structure of the solution should mimic that of the problem.
Its also easier to start with a state diagram, defining each state with arcs between them representing each valid state transition. Great for team and user discussions.
Make it as simple as possible, but useful
correct me if I'm wrong but I think the downside in this example of using an enum is that you can't implement a trait for an enum variant specifically, so you would have to implement methods differently, probably with a Result type as output
@@Roms8313 Yes I agree. I think the object oriented version makes it easier to dynamically alter the ‘states’ by adding or removing ‘state objects’ . Whereas if an enum is used to define the set of states it can become more static in nature. But of course there may be mechanisms to extend set of enumeration values 🙂, which would have the benefit of making the evolution of the program more evident. My preference just happens to encourage the simplicity and visual clarity of the program as a whole throughout its lifetime of use and adaption to user and platform needs as they evolve. I always try to have compassion for the unfortunate maintainer who some time in the future has to review the implementation, understand it and its implications (performance, limits, testability …) and the add in support for correcting implementation errors and/or new features.
I was about to make this comment. The approach Bogdan used is very OOP-esque and not particularly idiomatic in my opinion.
Personally I would much prefer to use a non-exhaustive enum wrapped in a Post struct in this particular case, and put all the business logic within methods of Post.
This has three main advantages:
1. Less code duplication and boilerplate code
2. Static dispatch, therefore more efficient
3. The caller is better able to get feedback on the status of each transaction, if those methods return a Result type. (Although I suppose that's possible too in Bogdan's approach)
I agree. I would have gone for enum too.
I personally think that the state pattern is pretty ugly, specially if we need to deal with Rust details using things like Options and Boxes, but it does provide some maintainability advantages. Imagine we would add more states.
With the enum approach, we would have a bunch of match expressions in all of our methods, and so we would need to go through each and every one of these methods and add arms correponding to the new states.
Using the state pattern approach instead, we would only need to implement methods in the new states and possibly modify some other states.
Excellent stuff. Thank you. I had had the opportunity to put to use principles of FSM in designing software for Credit-Card Auth Protocol, CDR processing in Telelcom, Online Multiplayer Gaming, Contests in Training by Gamification etc., using 'C', 'Java' and 'Scala'. I am learning Rust and this presentation guides me towards building similar software in Rust.
Letting Types do that talking, in a manner of speaking, is not straightforward in my experience but once done, its adoption benefits are manifold.
Beautifully explained! Thanks for taking the time to put this together. I’m really enjoying the content.
wow I am new to rust and I see still I need to learn a lot of concepts. Thanks for this video.
Freaking amazing !! You have comeup with a very simple example to explain a complex apttern, Need more like this rusty !!
Wow. Much needed video. I have been trying to learn these patterns for a while. This video explains state pattern very nicely and I understood more than I did by reading other materials. This video also shows rust having better approaches, which blew Mind blown.
Thanks a lot.
What do I search for to learn more on such 'rust patterns'?
this one was over the head , may be I need to revisit it again in some days..
Hey, man.
You know what's going to be interesting... Talk about data-oriented in RUST.
I'm doing research on this topic but is not easy to have examples...
Try checking for some ECS, it's a key concept of data oriented programming, although mainly used in game programming. The idea is to reduce cache misses by keeping every component in it's own list, then making systems that are basically some queries to get logic done. An entity is just a number, so it can be copied easily.
If it's too hard to start with, try beginning by understanding what are generational arenas allocators. You can check Hecs for an ECS in rust. And there is some great talks by professionals on the fields about this.
Hope it helps :)
@@ilyasb4792 That was aweasome man, thkx!
Hmm. I've really been trying to make the similarish builder-pattern work, but I have to admit I never found a situation where I did not eventually conclude that other abstractions proved superior.
It's just that you end up with a bunch of different initialization states that make predictable behavior difficult, and you end up with a lot of methods that are meaningless after being called the first time.
Edit:
Yeah, the encoded in Type variant is more like what I've concluded is to be done if I really need it.
Doesnt' it result in a new copy of all members, every time when the state is changed? As each state is different type, holding all needed members.
@@Nuclear868 Well, probably yeah. The other pattern might just do the same though, and also thb copying is usually cheap compared to heap allocations. And the way at least Rusty implemented it the original pattern causes a lot of allocations, where the updated one causes none at all, because it doesn't use Box. At least IIRC.
@@Nuclear868 Well, revisiting it, what I said is the case.
Excluding string allocations that happen in any case.
Although, also, I'm not sure how Rust treats these zero size struct in boxes, it might optimize them away.
TBH I would not think about optimizations when any of these patterns are regarded. They're made to be easy to use, not to perform well, and their usage does not tend to end up in hot loops, if they do you should rethink.
The second approach switching types is looks pretty robust. For code where I want really strict safety type checks during compile time, this might be way to go.
Type state pattern would be ideal here I think
Hi nice Video again! Why not invest in some light setup? There are cheap sets that make a lot difference.
I was able to replicate code and make a ASCII animation with it. Thanks for the video really helpful stuff.
Hey Man. I can’t do much for you than thanking you so much. ❤️
Keep it coming ⭐️
Ok this is a hard to understand , but gonna push through it
NGL you’re very good and consistent do you write c or c++
Use an Enum, Bogdan!
great vid! That example is similar to the one on The Book, did you take it from there? Also I like your font, what's its name?
This is taken directly from The Book and he has a series of videos going through each chapter: th-cam.com/play/PLai5B987bZ9CoVR-QEIN9foz4QCJ0H2Y8.html
Hey! How would you use macros to avoid repetitive code (request_review and approve functions) in this example?
I don't get this. Why don't you just use an enum?
As you could see in the video, there are many ways to get the same outcome. The state design pattern provided encapsulation, that you would lose if you just used an enum. Meaning, if every function has a huge switch statement, all the logic of every state is inside that function, instead of being distributed in each state. If states are complex, separating/encapsulating things may be desirable. Also, the second aproach is also another way of solving the same situation, using the type system. There are always many ways to do the same, so ideally you should know a bunch and waver their pros and cons to use the one that better suits your needs.
As always, GREAT content. Really gave allot of great concepts that I have been working with myself during my development of my task manager.
A few notes, while I know you end up changing the implementation in the second part, the state field gave me major "maybe not?" vibes: th-cam.com/video/YR5WdGrpoug/w-d-xo.html. as a general rule, I try to avoid maybe types like Option within the actual struct body (see video for reasons why).
Your second example was probably more idiomatic, but I think a small change you could make is giving a reference to self, rather than having the function consume self (or even the mutable reference for that matter) and simply return a NEW variant of the object with the required changes.
This ends up being MUCH less finickey with ownership since you no longer need to think about the ownership change flow when putting this into a larger program, these methods can be chained or done atomically as one could see fit. And while the idea of the old version being consumed seems more elegant, there are often many good reasons to keep the former version of the object around just in case you need to use it some more (a great example is a comparison function where you tell the user that you converted an object from one state to another or something). Finally, if we end up not needing the old object after we get the new one, it will simply be cleaned up at the end of the function due to rust's dropping mechanism.
While a die-hard performance buff would argue that creating a new object is inefficient, I have found that there is no real performance loss in day-to-day use unless you are REALLY resource constrained, and in exchange you get a functional layout that makes reasoning about an object's ownership status at any time trivially easy (even within an async workflow since there are almost no locks from an object holding a mutable reference hostage from the other threads).
This was a GREAT showcase of how the object design pattern is implemented in rust and i'm excited for more content.
4:29 you could have used Self instead of Post there.
love your videos and rust :)
You are a gem
Why do we use "state: Option"? why cannot we use "state: Option" ?
(To anyone) Would there be any advantage to put all the state types into the fields an enem ? You could use match on the variant to choose which method to run and all enum variants would have to be covered or you would get an error.
Request for unsafe!
Coming soon!
Yay, another video!
By the way, why do we need to provide our email for the cheat sheet, what are you doing with that information? "I have been fully informed and consent to the collection and use of my personal data **for any purpose in connection with the software, products and/or services**." seems pretty wide.
Why we need to implement function if it has no affect? Just return self... Why just don't implement it at all?
Because we are relying on each state to have that method, even if it does nothing, since we are calling those methods independently of the state the post is in.
insta subscribe :) Keep the good content coming!
Welcome aboard!
that is cool!
hey is there any chance of you publishing these explaining videos' source code into github or something? I wanted to look deeper and play around with the code myself
anyway, great videos! I hope your channel blow up very soon :)
You can get the code from the Rust Book: doc.rust-lang.org/stable/book/ch17-03-oo-design-patterns.html
Functional approach in Rust is much better.
What problem does this approach solve? This seems overly complicated for what it is doing.
Big D energy
always a gem.,
Honestly, that specific case could be better by just using enums
what is your font name?
Wut, I have never heard of a State Design Pattern...but then again I am a noob :D
In the OO approach you have a circular dependency between Post and State. Is it possible to implement State without any knowledge of Post?
all really cool but why not just use enums for the state... Why is the pattern even needed in the beggining
Is it not possible to construct all these structs without calling new methods? That way we can bypass the whole state system an e.g. construct a Post right at the spot.
молодець
maybe slow down a bit and explain more?
First example with unified interface quickly backfires - returning empty string as "special value" instead of Option is sketchy AF. The only advantage of this approach I see is that Box logic is hidden from library user. While there may be situation in second solution where user would have to use Box to encapsulate all different state types himself.
BTW: I love this taking ownership of self trick. It makes second solution so bomb-proof just by pure language design. You cannot accidentally "fork" states and keep using old one and new one at the same time.
ngl, you always look high af