Improve your Rust APIs with the type state pattern
Жүктеу.....
Пікірлер: 276
@letsgetrusty Жыл бұрын
📝Get your *FREE Rust cheat sheet* : www.letsgetrusty.com/cheatsheet Corrections: 10:00 - PhantomData isn't needed here. The Locked and Unlocked structs are already zero-sized types. 11:40 - The lock method should return PasswordManager
@kennethbeal
Жыл бұрын
That was great, thank you! Amazing how it catches so many errors before even running; and, the hints are dynamic as well. Nicely demonstrated.
@xokelis0015
2 ай бұрын
There is a bug in your final lock() method on lines 27 and 31, since it just returns an unlocked password manager. You should change the state to locked in both lines. Other than that minor issue, great vid.
@jeffg4686 Жыл бұрын
I like the idea of using "types" as "markers" of sorts (regarding using generics on a struct just for differentiation). Better than using a field as that's runtime data.This was a great little video. Helped me see things a little different. A "rust patterns" mini-series would be good. Definitely some distinctions from other languages.
@shinobuoshino5066
4 ай бұрын
Lmao, this was done in OOP for past 50 years.
@1____-____1
28 күн бұрын
@@shinobuoshino5066What does OOP have to do with the topic of the video? This is about encapsulation verification (access control) at compile time via Rust's type system. How does OOP do the same via types?
@VictorGamerLOL Жыл бұрын
You mistyped the lock method it still returns an unlocked password manager
@letsgetrusty
Жыл бұрын
Good catch, it should be locked.
@mihaigalos279
Жыл бұрын
@@letsgetrustyI suggest to always return Self to simplify refactoring and guard against such cases for the future. One can also construct and return a Self {}.
@brdrnda3805
Жыл бұрын
@@mihaigalos279 Isn't Self the wrong type? If you call unlock then Self is PasswordManager but you want to return a PasswordManager
@FryuniGamer
Жыл бұрын
@@mihaigalos279 can't use Self when changing the state since Self represent the type with the current state
@Galaf
Жыл бұрын
Thank you, I thought I was going crazy.
@EngineerNick Жыл бұрын
Brilliant thank you! PyO3 (the rust crate for python extensions) uses this method to confirm the user has the GIL (Global interpreter Lock). At the time I gave up trying to understand the phantom data, but thanks to you I understand now! Now if only there was a way to make the macros that crate uses less mysterious...
@Ether_Void Жыл бұрын
Soon it might also be possible to define states in an enum using const generics. Currently this requires the unstable "adt_const_params" feature, otherwise you could only be allowed to use integer and char types which isn't really readable. This would have the benefit that it's easier to see what states are possible currently there is no real connection between the Locked and Unlocked types and the PasswordManager other than the impl blocks.
@diarmaidmac2149 Жыл бұрын
Amazing video. Thank you! This solution is very elegant. Thank you for exploring the initial, non-optimal solutions first. It makes it easy to see the benefits of the final solution.
@jonesmartins Жыл бұрын
This is the first time I understand PhantomData. Thanks!
@TehKarmalizer Жыл бұрын
It’s a beautiful use of the type system. Definitely gives me something to think about.
@varantavers Жыл бұрын
Recently I have came across a situation where I had to do something similar and this video immediately came to my mind. Nice work!
@henrywang6931 Жыл бұрын
The type state pattern, love it!
@trashhater9304 Жыл бұрын
This is awesome! Almost the same thing was implemented in ATS2 Postiats socket library, which is basically a header files for interop between "C". It uses something like the phantom types but with the refinement types. So not only you can describe the exact flow for you api, but also bind this flow with actual values you pass into functions. This is done via dependent type system
@lukasz_kostka Жыл бұрын
Fantastic. I love short, to the point tutorials like this.
@embeddedbastler6406 Жыл бұрын
As an alternative implementation, we could have the unlock method return an owned token that is a required parameter for the list_passwords method. Upon locking, the token is consumed again by the PasswordManager.
@GrzesiuG44
Жыл бұрын
This is definetly the proper way to solve this specific problem. Not only you get compile time checks for API usage, but your API is now closer to supporting being unlocked independently in two different functions.
@tubebrocoli
Жыл бұрын
@@GrzesiuG44 however, this requires allocating extra data, which isn't what the demo was trying to demonstrate.
@oleh1
10 ай бұрын
@@tubebrocoli cannot this token be a zero-sized struct too? "struct Token;" If it can, how does passing a zero-sized struct as a function parameter affect the function's call stack?
@TheZdannar Жыл бұрын
This video is hot sauce. I had seen Phantom data before, but did not fully comprehend it. Nice work Bogdan.
@goodwish1543 Жыл бұрын
Beautiful! Thanks for sharing. Looking forward to more intermediate Rust contents like this. :)
@codebycarlos7 ай бұрын
Wow man. First 12 hours with Rust and it has already blown my mind so many times.
@schred Жыл бұрын
Your best video yet in my opinion, thanks for your work! :D
@AntonKravc Жыл бұрын
Great video! I’m just starting to learn rust but everything was explained very clearly. Coming from typescript I’m very excited about the existence of PhantomData.
@Charls93xx Жыл бұрын
That was super straightforward, thanks for sharing!
@nirmalyasengupta6883 Жыл бұрын
Zero-size Types is new to me. I have learnt something. Thanks. I have used similar approaches in Scala / Java earlier, even though the effort was more. Moreover, in Akka-Typed, the approach is very similar, even though the implementation is cumbersome. Thanks again, for uploading this.
@andredasilva6807 Жыл бұрын
didnt know about zero types. really nice video. as always keep the great work up
@kwinzman Жыл бұрын
I've been doing that instinctively for a long time now. But great example and explanation nontheless! The Rust specifics with the zero-size types was new and pretty informative!
@JohnDoe-ji1zv Жыл бұрын
Nice video, really useful, didn’t know about this pattern and state before. Cheers
@jonnyso1 Жыл бұрын
That's the exact feature I needed the other day and I didn't know yet !
@leonardogomes51212 ай бұрын
Great! Great! Great! The best explanation of the Type State Pattern on KZread! You rock man! How can we support your work?
@chillyvanilly6352 Жыл бұрын
Wow, that was genuinely an awesome vid, thank you!
@Jiftoo10 ай бұрын
I use this pattern for data sanitisation in my backend. Works great!
@kajacx Жыл бұрын
Nice, saw this once before in a "chaining builder" pattern that prevented setting the same property twice. I personally would make UnlockedManager a newtype for &mut PasswordManager, that way, do don't need ownership and you get the re-lock for free when it drops.
@debuti Жыл бұрын
I think this is maybe your best video
@timvw01 Жыл бұрын
Interesting pattern! My default is to revert to state variables, usually enums.
@AceofSpades5757 Жыл бұрын
I haven't seen this one before. Very cool!
@irlshrek Жыл бұрын
this was a really good explanation!! thanks for this
@AlwaysStaringSkyward Жыл бұрын
I learned a lot from this. Thank you!
@JUMPINGxxJEFF Жыл бұрын
Nicely explained, thanks
@miriamramstudio3982 Жыл бұрын
Woow, that was very cool. Great teaching. Thanks
@xavhow Жыл бұрын
This is so useful! Thank you for sharing.
@mustafazakiassagaf1757 Жыл бұрын
i like this kind of video. keep up the good work man
@EdgeGaming Жыл бұрын
Beautiful. Will be looking for ways to use this, thanks!
@kopuz.co.uk.
Жыл бұрын
you code your password vaults like this? 😱
@ianknowles Жыл бұрын
Always handy Bogdan appreciate the share!
@jma42 Жыл бұрын
thanks for this, I really wondered how typestate pattern works since it was said that it only works well with rust..
@vanish3408 Жыл бұрын
I'm pretty sure you don't have to specify the type of PhantomData since it would be done by the compiler, aside from that very small thing, this is one of your best videos so far. It's providing information on an intermidiate level while being explained very well!
@julytikh
Жыл бұрын
Indeed, usage of `PhantomData` seems redundant since `State` is already a zero-sized type. (BTW, there is another error: in the third solution, the method `lock()` should return a `PasswordManager`, not `PasswordManager`.)
@user-bj2tz8qg6g
9 ай бұрын
@@julytikh Yeah, I also noticed both errors. But the video is still great!
@tri99er_
5 ай бұрын
I'm new to Rust, but I felt weird, when he said, that just using "State" would take up memory, since Locked amd Unlocked are zero sized types, just like PhantomData. I'm glad to see, that I wasn't mistaken. What are idiomatic uses of PhantomData then? I know, it can "own" nonzero size types (e.g. PhantomData), but when is it useful compared to using just plain unit structs? The only thing I can see, is if somebody by mistake created PasswordManager (which wouldn't be prevented by compiler), they'd be wasting some memory in case, where "state: State" ("state: i32": size is equal to size of i32), as opposed to "state: PhantomData" ("state: PhantomData": size is 0). But there's a way to restrict the State generic to only include the right types, which would prevent you from creating idiotic types like PasswordManager, which would be even better, than using PhantomData to deal with it. You can create a (possibly private) marker trait yourself, let's call LockState and have Locked and Unlocked implement it, and then restrict generic State: LockState. At that point there's no need to use PhantomData in this case, and it's absolutely impossible to create idiotic types (e.g. PasswordManager). I guess PhantomData is useful in cases, where your intent is for your type to be able to be marked by any types user of your library wants (e.g. YourType, YourType, YourType). But I don't know of such scenario (because YourType would not actually contain values of those types, it's only marked by them).
@MrVulfe
5 ай бұрын
@@tri99er_ One case where this comes up occasionally is when you need a type dependency for the working of your type (e.g. for a return type in one of its methods, for example) but the type isn't actually used in the struct itself. In that situation, you can use a PhantomData to satisfy the compiler that you are "using" the type in the struct so that you can use the parameter in your methods.
@julians.2597
2 ай бұрын
@@tri99er_ e.g. the implementation of 'dyn Trait'
@Chastor97 Жыл бұрын
Till the end I have been waiting for raii cause I think it fits here well
@flippert06 ай бұрын
Very interesting and illuminating ! A while back ago, I was heavily into generating code from UML state diagrams. Generated state machine code either would use a traditional state transition table or state classes. Now duplicated code isn't that much of a problem, if the code is generated anyway, however to avoid duplicatation I could also a use traditional inheritance. So "LockedPasswordManagerr" and "UnlockedPasswordManager" would both inherit from "PasswordManager" which would implement common code (here: "version()" and "encryption"). It never occurred to me, that I could do the same with generics.
@tri99er_
5 ай бұрын
There's no inheritance in Rust. And traits don't have access to implementor fields, so you'll end up with the same amount of duplication.
@AveN7ers Жыл бұрын
Very cool stuff man
@minciNashu Жыл бұрын
Would be interesting to expand on this further by implementing auto lock on drop, raii style.
@kajacx
Жыл бұрын
Just make LockedPasswordManager a newtype for &mut PasswordManager. That way, it makes the locked manager avaliable after it gets dropped automatically thanks to Rust's ownership system.
@MrTrollland
10 ай бұрын
u can just impl the Drop trait cant u
@setoelkahfi Жыл бұрын
This been really useful. Thanks!
@TadaHrd Жыл бұрын
This was relay helpful. I couldn't come up with such a smart system. However, you should have a State trait that is implemented for the two state types.
@Echiduna Жыл бұрын
Enjoy this video so much!
@GAGONMYCOREY Жыл бұрын
This video is one of your best
@Archepter Жыл бұрын
This was very cool. Thanks !
@thanhatpham3428 Жыл бұрын
Thank you, now i know how to do something like inheritance in rust
@MrLunarpulse Жыл бұрын
Awesome this is the idiomatic way of using Rust type system for states. It van be also extended to state machine or state chart.
@heret1c385Ай бұрын
great pattern. Man, I just love rust.
@alexandersemionov5790 Жыл бұрын
interesting, looks like a builder pattern, but returns an instance of same struct with just a state 0 size in memory and template reference thing. Great combo
@MiterAlmeida Жыл бұрын
Loved this video! ❤
@Tferdz Жыл бұрын
Please use Self as a return from your methods, instead of rewriting all the time the struct name. It makes code reformatting wayyyy easier! :)
@RoyalVideoPresents
Жыл бұрын
Why? If you rename the struct shouldn‘t the IDE rename that too?
@DavidM_603
Жыл бұрын
lock() and unlock() do not return Self, they change the type
@samansamani4477 Жыл бұрын
Thank you, this can become handy
@haydn.murray Жыл бұрын
This was great!
@_jdfx Жыл бұрын
Really cool! thanks Bogdan!
@ShreksSpliff Жыл бұрын
In some other languages like Haskell they call it the Indexed Monad pattern. Feels pretty similar to me. The Rust library called Graph uses this in their builder pattern graph constructor, so it can infer at the type level if your graph is directed or undirected, and if the edges or nodes contains values. Also, thanks for this. The non generic example was a nice touch. Should it be put in the lock impl, as it defaults to lock or is this better as it changes with the default state?
@chessai2121
Жыл бұрын
Indexed Monads are an extension of "plain"-er GADTs with DataKinds. Before those were usable, people used open phantom types. GADTs + DataKinds are more common than indexed monads because a lot of operations can be represented with simple non-monadic functions.
@ImranSheikh-kg4qd Жыл бұрын
I really like this kind of videos 🤛
@banniball Жыл бұрын
I liked the general concepts of using types to represent the state. But wouldn't one problem be that after locking the password manager one could still have a reference to the unlocked one. Since lock/unlock returns a new instance
@banniball
Жыл бұрын
I.e it's not like an FSM with one state but rather a struct where you can have multiple instances all with same content with different state.
@MatthiasBlume
5 ай бұрын
That's the whole point: lock and unlock take their self argument not by reference but by ownership transfer (i.e., move). So the caller loses ownership, and there cannot be any references to the old instance or the whole thing does not typecheck.
@antoniong43808 ай бұрын
Wtf. You can actually do this? So much potential! And head wacking because I need to make sense of the structure
@kirillgimranov4943 Жыл бұрын
Nice stuff, it'll help a lot
@stephenreaves3205 Жыл бұрын
This was beautiful
@rsalmei Жыл бұрын
It does not need both the default generic type and the last impl block. Just define `new()` in whatever state you want it, e.g. in the `impl PasswordManager` block.
@sgq995 Жыл бұрын
Great video!
@jameskoh34633 ай бұрын
I like TypeScript exactly because it provides similar functionality to implement this 👍
@timvw019 ай бұрын
In the last example, if you switch states, does rust have to move all the data to the new struct? So its basically allocating a whole new object when switching states?
@Matt23488 Жыл бұрын
In the third solution, you didn't update the lock() method to return a locked password manager. But it doesn't matter, you got the point across and I bet many people didn't even notice.
@ddystopia8091 Жыл бұрын
Incredible!
@cameronhunt5967 Жыл бұрын
Does the compiler help make sure you handle all possible states for structs as it does in match statements?
@jaumesinglavalls5486 Жыл бұрын
why the lock method returns the state Unlocked? It should return the state Locked, right? Also, in rust should be possible to force the manager cannot be used after calling to lock or unlock? (to force do the switch of the variable?)
@nocodenoblunder6672 Жыл бұрын
This looks amazing. But is only suitable for state that changes infrequently right? Whenever we transform from one state to another, we are creating a copy of the structs fields
@adambutler2646 Жыл бұрын
great video. thank you
@nerdbot4446 Жыл бұрын
Can you also create an impl block that defines methods for multiple states? There might be methods that shouldn't exist in all states, but in some.
@martinbecker1069 Жыл бұрын
This perfectly explains Phantom Data types, I never really grokked this but this instantly made me understand and Now I can see how it can be used in other places! It kind of reminds me of how Two structs of the same Type but with different life-times are treated as two completely separated types so you can't return something with the wrong life-time.
@julytikh
Жыл бұрын
Actually, `PhantomData` is redundant in this particular case. The types `Locked` and `Unlocked` are already zero-sized (because they have no fields), and `PhantomData` does not improve upon that.
@LukasCobblerxD
11 ай бұрын
@@julytikh can you explain how not to use the phantom data in this example
@julytikh
11 ай бұрын
@@LukasCobblerxD just use `State` instead of `std::marker::PhantomData`.
@Bonta768 Жыл бұрын
Excellent. I really enjoyed learning this one. What minimum version of Rust can you use this pattern on?
@EvanBoldt
Жыл бұрын
Looks like the PhantomData struct is since 1.0.0. I don’t see when default generics were added but there’s a spec for it from 2015.
@Spiun66611 ай бұрын
Given that you consume the original password manager, is it necessary to clone the members of the struct (password list and string)
@NoahSteckley Жыл бұрын
Would an enum LockedStatus work well here too?
@erlangparasu6339 Жыл бұрын
thank you so much 🙏
@wumwum42 Жыл бұрын
I think in the final solution, you can merge the first and 4th impl. PasswordManager and PasswordManager is the same
@rad9587
Жыл бұрын
This is correct, but I think it's clearer, because we are not creating a locked manager, but just a manager(Even though it will be locked anyway)
@alerya100Ай бұрын
Amazing !
@CYXXYC Жыл бұрын
how would using just state: State waste memory? Isn't it also 0-sized?
@koonoho
Жыл бұрын
I'm also confused about this. I can certainly see PhantomData being useful if the struct holds some data, but in this case there really shouldn't be any additional memory used.
@pawe460
Жыл бұрын
I checked it on goldbolt and both structures with :State and :PhantomData takes 72 bytes for me, so I guess it is optimized out in both variants
@Baptistetriple0
Жыл бұрын
What he is saying is very confusing, if T is a ZST you don't need PhantomData to make it ZS because it already is, PhantomData exist primarly for lifetimes: for example there are structs that owns a pointer for optimizations, but still need to hold the lifetime of the backing data, so you add somewhere a PhantomData
@letsgetrusty
Жыл бұрын
It wouldn't, I made a mistake. Will point this out in the pinned comment.
@ahmedhassanahmedhassan6495 Жыл бұрын
Thanks 4 z great videos :)
@skpz7335 Жыл бұрын
Nice video thanks
@mmssus10 ай бұрын
Question here though, why PasswordManager implementation for the constructor doesn't need a generic like the common methods implementation "encryption" and "version"?
@b_mathemagicalАй бұрын
This is great. Quick question (for anyone): why have an impl block with the generic state and then one for the type as a whole? E.g. why can’t the new method live in the generic impl block? Or vice versa - why can’t the shared methods live in the impl block for PasswordManager?
@codeman99-dev Жыл бұрын
What's the advantage over just using an enumeration of the password manager?
@officialismailshah Жыл бұрын
Best vid❤❤❤❤
@Beastpig41 Жыл бұрын
Great video
@loganhodgsn Жыл бұрын
Since Locked and Unlocked are Zero-Sized Types (ZST's), why do we need to use PhantomData? What if our state needed to carry a little bit of information? (such as who unlocked it)
@FlooferLand9 ай бұрын
Another solution would be to use Enums. You could have an enum called PasswordManager that has 2 states, one "Locked" and one "Unlocked", both of them containing a ManagerInfo struct. You could have a lock/unlock method for the enum which could do `let manager = PasswordManager::Locked(manager.0);` Doesn't really solve the main issue, just adding to the pile XD
@tri99er_
5 ай бұрын
Enum variants can't have unique implementations, so it doesn't really solve anything at all.
@derwintromp146111 ай бұрын
Can someone explain to me why at 11:49 he's returning PasswordManager in the lock function, shouldn't it be PasswordManager since the lock function should return a locked password manager?
@jay-tbl Жыл бұрын
How do you include another generic type with bounds? Say I want my password manager to also store another value, and I want that value to implement a list of traits. In each impl block I would have to specify that my generic type needs to implement the traits. How can I do this without repitition?
@jeffjia10611 ай бұрын
can we make Locked, Unlocked from struct to enum?
@Chastor97 Жыл бұрын
Wow. It is interesting 😲
@prashlovessamosa Жыл бұрын
OP Thumbnail
@skyeplus Жыл бұрын
This example is consuming previous state, which is fine. Can you make a proxy type holding reference instead?
@julians.2597
2 ай бұрын
it intentionally consumes self to prevent dangling references to previous states
Пікірлер: 276
📝Get your *FREE Rust cheat sheet* : www.letsgetrusty.com/cheatsheet Corrections: 10:00 - PhantomData isn't needed here. The Locked and Unlocked structs are already zero-sized types. 11:40 - The lock method should return PasswordManager
@kennethbeal
Жыл бұрын
That was great, thank you! Amazing how it catches so many errors before even running; and, the hints are dynamic as well. Nicely demonstrated.
@xokelis0015
2 ай бұрын
There is a bug in your final lock() method on lines 27 and 31, since it just returns an unlocked password manager. You should change the state to locked in both lines. Other than that minor issue, great vid.
I like the idea of using "types" as "markers" of sorts (regarding using generics on a struct just for differentiation). Better than using a field as that's runtime data.This was a great little video. Helped me see things a little different. A "rust patterns" mini-series would be good. Definitely some distinctions from other languages.
@shinobuoshino5066
4 ай бұрын
Lmao, this was done in OOP for past 50 years.
@1____-____1
28 күн бұрын
@@shinobuoshino5066What does OOP have to do with the topic of the video? This is about encapsulation verification (access control) at compile time via Rust's type system. How does OOP do the same via types?
You mistyped the lock method it still returns an unlocked password manager
@letsgetrusty
Жыл бұрын
Good catch, it should be locked.
@mihaigalos279
Жыл бұрын
@@letsgetrustyI suggest to always return Self to simplify refactoring and guard against such cases for the future. One can also construct and return a Self {}.
@brdrnda3805
Жыл бұрын
@@mihaigalos279 Isn't Self the wrong type? If you call unlock then Self is PasswordManager but you want to return a PasswordManager
@FryuniGamer
Жыл бұрын
@@mihaigalos279 can't use Self when changing the state since Self represent the type with the current state
@Galaf
Жыл бұрын
Thank you, I thought I was going crazy.
Brilliant thank you! PyO3 (the rust crate for python extensions) uses this method to confirm the user has the GIL (Global interpreter Lock). At the time I gave up trying to understand the phantom data, but thanks to you I understand now! Now if only there was a way to make the macros that crate uses less mysterious...
Soon it might also be possible to define states in an enum using const generics. Currently this requires the unstable "adt_const_params" feature, otherwise you could only be allowed to use integer and char types which isn't really readable. This would have the benefit that it's easier to see what states are possible currently there is no real connection between the Locked and Unlocked types and the PasswordManager other than the impl blocks.
Amazing video. Thank you! This solution is very elegant. Thank you for exploring the initial, non-optimal solutions first. It makes it easy to see the benefits of the final solution.
This is the first time I understand PhantomData. Thanks!
It’s a beautiful use of the type system. Definitely gives me something to think about.
Recently I have came across a situation where I had to do something similar and this video immediately came to my mind. Nice work!
The type state pattern, love it!
This is awesome! Almost the same thing was implemented in ATS2 Postiats socket library, which is basically a header files for interop between "C". It uses something like the phantom types but with the refinement types. So not only you can describe the exact flow for you api, but also bind this flow with actual values you pass into functions. This is done via dependent type system
Fantastic. I love short, to the point tutorials like this.
As an alternative implementation, we could have the unlock method return an owned token that is a required parameter for the list_passwords method. Upon locking, the token is consumed again by the PasswordManager.
@GrzesiuG44
Жыл бұрын
This is definetly the proper way to solve this specific problem. Not only you get compile time checks for API usage, but your API is now closer to supporting being unlocked independently in two different functions.
@tubebrocoli
Жыл бұрын
@@GrzesiuG44 however, this requires allocating extra data, which isn't what the demo was trying to demonstrate.
@oleh1
10 ай бұрын
@@tubebrocoli cannot this token be a zero-sized struct too? "struct Token;" If it can, how does passing a zero-sized struct as a function parameter affect the function's call stack?
This video is hot sauce. I had seen Phantom data before, but did not fully comprehend it. Nice work Bogdan.
Beautiful! Thanks for sharing. Looking forward to more intermediate Rust contents like this. :)
Wow man. First 12 hours with Rust and it has already blown my mind so many times.
Your best video yet in my opinion, thanks for your work! :D
Great video! I’m just starting to learn rust but everything was explained very clearly. Coming from typescript I’m very excited about the existence of PhantomData.
That was super straightforward, thanks for sharing!
Zero-size Types is new to me. I have learnt something. Thanks. I have used similar approaches in Scala / Java earlier, even though the effort was more. Moreover, in Akka-Typed, the approach is very similar, even though the implementation is cumbersome. Thanks again, for uploading this.
didnt know about zero types. really nice video. as always keep the great work up
I've been doing that instinctively for a long time now. But great example and explanation nontheless! The Rust specifics with the zero-size types was new and pretty informative!
Nice video, really useful, didn’t know about this pattern and state before. Cheers
That's the exact feature I needed the other day and I didn't know yet !
Great! Great! Great! The best explanation of the Type State Pattern on KZread! You rock man! How can we support your work?
Wow, that was genuinely an awesome vid, thank you!
I use this pattern for data sanitisation in my backend. Works great!
Nice, saw this once before in a "chaining builder" pattern that prevented setting the same property twice. I personally would make UnlockedManager a newtype for &mut PasswordManager, that way, do don't need ownership and you get the re-lock for free when it drops.
I think this is maybe your best video
Interesting pattern! My default is to revert to state variables, usually enums.
I haven't seen this one before. Very cool!
this was a really good explanation!! thanks for this
I learned a lot from this. Thank you!
Nicely explained, thanks
Woow, that was very cool. Great teaching. Thanks
This is so useful! Thank you for sharing.
i like this kind of video. keep up the good work man
Beautiful. Will be looking for ways to use this, thanks!
@kopuz.co.uk.
Жыл бұрын
you code your password vaults like this? 😱
Always handy Bogdan appreciate the share!
thanks for this, I really wondered how typestate pattern works since it was said that it only works well with rust..
I'm pretty sure you don't have to specify the type of PhantomData since it would be done by the compiler, aside from that very small thing, this is one of your best videos so far. It's providing information on an intermidiate level while being explained very well!
@julytikh
Жыл бұрын
Indeed, usage of `PhantomData` seems redundant since `State` is already a zero-sized type. (BTW, there is another error: in the third solution, the method `lock()` should return a `PasswordManager`, not `PasswordManager`.)
@user-bj2tz8qg6g
9 ай бұрын
@@julytikh Yeah, I also noticed both errors. But the video is still great!
@tri99er_
5 ай бұрын
I'm new to Rust, but I felt weird, when he said, that just using "State" would take up memory, since Locked amd Unlocked are zero sized types, just like PhantomData. I'm glad to see, that I wasn't mistaken. What are idiomatic uses of PhantomData then? I know, it can "own" nonzero size types (e.g. PhantomData), but when is it useful compared to using just plain unit structs? The only thing I can see, is if somebody by mistake created PasswordManager (which wouldn't be prevented by compiler), they'd be wasting some memory in case, where "state: State" ("state: i32": size is equal to size of i32), as opposed to "state: PhantomData" ("state: PhantomData": size is 0). But there's a way to restrict the State generic to only include the right types, which would prevent you from creating idiotic types like PasswordManager, which would be even better, than using PhantomData to deal with it. You can create a (possibly private) marker trait yourself, let's call LockState and have Locked and Unlocked implement it, and then restrict generic State: LockState. At that point there's no need to use PhantomData in this case, and it's absolutely impossible to create idiotic types (e.g. PasswordManager). I guess PhantomData is useful in cases, where your intent is for your type to be able to be marked by any types user of your library wants (e.g. YourType, YourType, YourType). But I don't know of such scenario (because YourType would not actually contain values of those types, it's only marked by them).
@MrVulfe
5 ай бұрын
@@tri99er_ One case where this comes up occasionally is when you need a type dependency for the working of your type (e.g. for a return type in one of its methods, for example) but the type isn't actually used in the struct itself. In that situation, you can use a PhantomData to satisfy the compiler that you are "using" the type in the struct so that you can use the parameter in your methods.
@julians.2597
2 ай бұрын
@@tri99er_ e.g. the implementation of 'dyn Trait'
Till the end I have been waiting for raii cause I think it fits here well
Very interesting and illuminating ! A while back ago, I was heavily into generating code from UML state diagrams. Generated state machine code either would use a traditional state transition table or state classes. Now duplicated code isn't that much of a problem, if the code is generated anyway, however to avoid duplicatation I could also a use traditional inheritance. So "LockedPasswordManagerr" and "UnlockedPasswordManager" would both inherit from "PasswordManager" which would implement common code (here: "version()" and "encryption"). It never occurred to me, that I could do the same with generics.
@tri99er_
5 ай бұрын
There's no inheritance in Rust. And traits don't have access to implementor fields, so you'll end up with the same amount of duplication.
Very cool stuff man
Would be interesting to expand on this further by implementing auto lock on drop, raii style.
@kajacx
Жыл бұрын
Just make LockedPasswordManager a newtype for &mut PasswordManager. That way, it makes the locked manager avaliable after it gets dropped automatically thanks to Rust's ownership system.
@MrTrollland
10 ай бұрын
u can just impl the Drop trait cant u
This been really useful. Thanks!
This was relay helpful. I couldn't come up with such a smart system. However, you should have a State trait that is implemented for the two state types.
Enjoy this video so much!
This video is one of your best
This was very cool. Thanks !
Thank you, now i know how to do something like inheritance in rust
Awesome this is the idiomatic way of using Rust type system for states. It van be also extended to state machine or state chart.
great pattern. Man, I just love rust.
interesting, looks like a builder pattern, but returns an instance of same struct with just a state 0 size in memory and template reference thing. Great combo
Loved this video! ❤
Please use Self as a return from your methods, instead of rewriting all the time the struct name. It makes code reformatting wayyyy easier! :)
@RoyalVideoPresents
Жыл бұрын
Why? If you rename the struct shouldn‘t the IDE rename that too?
@DavidM_603
Жыл бұрын
lock() and unlock() do not return Self, they change the type
Thank you, this can become handy
This was great!
Really cool! thanks Bogdan!
In some other languages like Haskell they call it the Indexed Monad pattern. Feels pretty similar to me. The Rust library called Graph uses this in their builder pattern graph constructor, so it can infer at the type level if your graph is directed or undirected, and if the edges or nodes contains values. Also, thanks for this. The non generic example was a nice touch. Should it be put in the lock impl, as it defaults to lock or is this better as it changes with the default state?
@chessai2121
Жыл бұрын
Indexed Monads are an extension of "plain"-er GADTs with DataKinds. Before those were usable, people used open phantom types. GADTs + DataKinds are more common than indexed monads because a lot of operations can be represented with simple non-monadic functions.
I really like this kind of videos 🤛
I liked the general concepts of using types to represent the state. But wouldn't one problem be that after locking the password manager one could still have a reference to the unlocked one. Since lock/unlock returns a new instance
@banniball
Жыл бұрын
I.e it's not like an FSM with one state but rather a struct where you can have multiple instances all with same content with different state.
@MatthiasBlume
5 ай бұрын
That's the whole point: lock and unlock take their self argument not by reference but by ownership transfer (i.e., move). So the caller loses ownership, and there cannot be any references to the old instance or the whole thing does not typecheck.
Wtf. You can actually do this? So much potential! And head wacking because I need to make sense of the structure
Nice stuff, it'll help a lot
This was beautiful
It does not need both the default generic type and the last impl block. Just define `new()` in whatever state you want it, e.g. in the `impl PasswordManager` block.
Great video!
I like TypeScript exactly because it provides similar functionality to implement this 👍
In the last example, if you switch states, does rust have to move all the data to the new struct? So its basically allocating a whole new object when switching states?
In the third solution, you didn't update the lock() method to return a locked password manager. But it doesn't matter, you got the point across and I bet many people didn't even notice.
Incredible!
Does the compiler help make sure you handle all possible states for structs as it does in match statements?
why the lock method returns the state Unlocked? It should return the state Locked, right? Also, in rust should be possible to force the manager cannot be used after calling to lock or unlock? (to force do the switch of the variable?)
This looks amazing. But is only suitable for state that changes infrequently right? Whenever we transform from one state to another, we are creating a copy of the structs fields
great video. thank you
Can you also create an impl block that defines methods for multiple states? There might be methods that shouldn't exist in all states, but in some.
This perfectly explains Phantom Data types, I never really grokked this but this instantly made me understand and Now I can see how it can be used in other places! It kind of reminds me of how Two structs of the same Type but with different life-times are treated as two completely separated types so you can't return something with the wrong life-time.
@julytikh
Жыл бұрын
Actually, `PhantomData` is redundant in this particular case. The types `Locked` and `Unlocked` are already zero-sized (because they have no fields), and `PhantomData` does not improve upon that.
@LukasCobblerxD
11 ай бұрын
@@julytikh can you explain how not to use the phantom data in this example
@julytikh
11 ай бұрын
@@LukasCobblerxD just use `State` instead of `std::marker::PhantomData`.
Excellent. I really enjoyed learning this one. What minimum version of Rust can you use this pattern on?
@EvanBoldt
Жыл бұрын
Looks like the PhantomData struct is since 1.0.0. I don’t see when default generics were added but there’s a spec for it from 2015.
Given that you consume the original password manager, is it necessary to clone the members of the struct (password list and string)
Would an enum LockedStatus work well here too?
thank you so much 🙏
I think in the final solution, you can merge the first and 4th impl. PasswordManager and PasswordManager is the same
@rad9587
Жыл бұрын
This is correct, but I think it's clearer, because we are not creating a locked manager, but just a manager(Even though it will be locked anyway)
Amazing !
how would using just state: State waste memory? Isn't it also 0-sized?
@koonoho
Жыл бұрын
I'm also confused about this. I can certainly see PhantomData being useful if the struct holds some data, but in this case there really shouldn't be any additional memory used.
@pawe460
Жыл бұрын
I checked it on goldbolt and both structures with :State and :PhantomData takes 72 bytes for me, so I guess it is optimized out in both variants
@Baptistetriple0
Жыл бұрын
What he is saying is very confusing, if T is a ZST you don't need PhantomData to make it ZS because it already is, PhantomData exist primarly for lifetimes: for example there are structs that owns a pointer for optimizations, but still need to hold the lifetime of the backing data, so you add somewhere a PhantomData
@letsgetrusty
Жыл бұрын
It wouldn't, I made a mistake. Will point this out in the pinned comment.
Thanks 4 z great videos :)
Nice video thanks
Question here though, why PasswordManager implementation for the constructor doesn't need a generic like the common methods implementation "encryption" and "version"?
This is great. Quick question (for anyone): why have an impl block with the generic state and then one for the type as a whole? E.g. why can’t the new method live in the generic impl block? Or vice versa - why can’t the shared methods live in the impl block for PasswordManager?
What's the advantage over just using an enumeration of the password manager?
Best vid❤❤❤❤
Great video
Since Locked and Unlocked are Zero-Sized Types (ZST's), why do we need to use PhantomData? What if our state needed to carry a little bit of information? (such as who unlocked it)
Another solution would be to use Enums. You could have an enum called PasswordManager that has 2 states, one "Locked" and one "Unlocked", both of them containing a ManagerInfo struct. You could have a lock/unlock method for the enum which could do `let manager = PasswordManager::Locked(manager.0);` Doesn't really solve the main issue, just adding to the pile XD
@tri99er_
5 ай бұрын
Enum variants can't have unique implementations, so it doesn't really solve anything at all.
Can someone explain to me why at 11:49 he's returning PasswordManager in the lock function, shouldn't it be PasswordManager since the lock function should return a locked password manager?
How do you include another generic type with bounds? Say I want my password manager to also store another value, and I want that value to implement a list of traits. In each impl block I would have to specify that my generic type needs to implement the traits. How can I do this without repitition?
can we make Locked, Unlocked from struct to enum?
Wow. It is interesting 😲
OP Thumbnail
This example is consuming previous state, which is fine. Can you make a proxy type holding reference instead?
@julians.2597
2 ай бұрын
it intentionally consumes self to prevent dangling references to previous states
Very nice