Improve your Rust APIs with the type state pattern

แชร์
ฝัง
  • เผยแพร่เมื่อ 2 ก.พ. 2025

ความคิดเห็น • 288

  • @letsgetrusty
    @letsgetrusty  ปีที่แล้ว +28

    📝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
      @kennethbeal ปีที่แล้ว +1

      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
      @xokelis0015 9 หลายเดือนก่อน

      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.

  • @RoyaltyInTraining.
    @RoyaltyInTraining. 6 หลายเดือนก่อน +23

    I instantly fell in love with this pattern as soon as I figured out what it's all about. It's the perfect use for Rust's amazing type system.

  • @jeffg4686
    @jeffg4686 ปีที่แล้ว +199

    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
      @shinobuoshino5066 11 หลายเดือนก่อน +3

      Lmao, this was done in OOP for past 50 years.

    • @LibreGlider
      @LibreGlider 8 หลายเดือนก่อน +3

      @@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?

    • @spaghettiking653
      @spaghettiking653 หลายเดือนก่อน

      @@shinobuoshino5066 Can you give an example?

  • @EngineerNick
    @EngineerNick ปีที่แล้ว +41

    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...

  • @VictorGamerLOL
    @VictorGamerLOL ปีที่แล้ว +323

    You mistyped the lock method it still returns an unlocked password manager

    • @letsgetrusty
      @letsgetrusty  ปีที่แล้ว +123

      Good catch, it should be locked.

    • @mihaigalos279
      @mihaigalos279 ปีที่แล้ว +38

      @@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
      @brdrnda3805 ปีที่แล้ว

      @@mihaigalos279 Isn't Self the wrong type? If you call unlock then Self is PasswordManager but you want to return a PasswordManager

    • @FryuniGamer
      @FryuniGamer ปีที่แล้ว +79

      ​@@mihaigalos279 can't use Self when changing the state since Self represent the type with the current state

    • @Galaf
      @Galaf ปีที่แล้ว +5

      Thank you, I thought I was going crazy.

  • @Ether_Void
    @Ether_Void ปีที่แล้ว +28

    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
    @diarmaidmac2149 ปีที่แล้ว +10

    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
    @jonesmartins ปีที่แล้ว +8

    This is the first time I understand PhantomData. Thanks!

  • @codebycarlos
    @codebycarlos ปีที่แล้ว +2

    Wow man. First 12 hours with Rust and it has already blown my mind so many times.

  • @varantavers
    @varantavers ปีที่แล้ว +2

    Recently I have came across a situation where I had to do something similar and this video immediately came to my mind. Nice work!

  • @embeddedbastler6406
    @embeddedbastler6406 ปีที่แล้ว +28

    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
      @GrzesiuG44 ปีที่แล้ว +5

      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
      @tubebrocoli ปีที่แล้ว

      @@GrzesiuG44 however, this requires allocating extra data, which isn't what the demo was trying to demonstrate.

    • @oleh1
      @oleh1 ปีที่แล้ว +1

      @@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?

    • @SundersBurrito
      @SundersBurrito 6 หลายเดือนก่อน

      Also you would only be able to list once. The impl in the video lets you use the unlocked manager as much as you want.

  • @vanish3408
    @vanish3408 ปีที่แล้ว +19

    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
      @julytikh ปีที่แล้ว +4

      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`.)

    • @divandrey-u3q
      @divandrey-u3q ปีที่แล้ว +1

      @@julytikh Yeah, I also noticed both errors. But the video is still great!

    • @tri99er_
      @tri99er_ ปีที่แล้ว

      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
      @MrVulfe ปีที่แล้ว

      @@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
      @julians.2597 9 หลายเดือนก่อน +1

      @@tri99er_ e.g. the implementation of 'dyn Trait'

  • @TehKarmalizer
    @TehKarmalizer ปีที่แล้ว +6

    It’s a beautiful use of the type system. Definitely gives me something to think about.

  • @henrywang6931
    @henrywang6931 ปีที่แล้ว +12

    The type state pattern, love it!

  • @adrianoalves-qripto
    @adrianoalves-qripto 11 วันที่ผ่านมา

    Oh my Gosh, that's just Awesome and Beautiful design Pattern. I can't stop to using it in every new module. As an example I'm currently developing a bot and defined my Candle struct using type state patterns to define when a Candle is open or closed. Amazing! Thank you!

  • @kajacx
    @kajacx ปีที่แล้ว +6

    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.

  • @ShreksSpliff
    @ShreksSpliff ปีที่แล้ว +44

    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
      @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.

  • @banniball
    @banniball ปีที่แล้ว +6

    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
      @banniball ปีที่แล้ว +1

      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
      @MatthiasBlume ปีที่แล้ว +8

      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.

  • @CYXXYC
    @CYXXYC ปีที่แล้ว +17

    how would using just state: State waste memory? Isn't it also 0-sized?

    • @koonoho
      @koonoho ปีที่แล้ว +1

      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
      @pawe460 ปีที่แล้ว +10

      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
      @Baptistetriple0 ปีที่แล้ว +3

      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
      @letsgetrusty  ปีที่แล้ว +4

      It wouldn't, I made a mistake. Will point this out in the pinned comment.

  • @kelechiukah8025
    @kelechiukah8025 6 หลายเดือนก่อน +1

    Nice video. Would love to see a split screen or have one pane zoomed in, one pane zoomed out, because i found it hard to do global reasoning when we move around with only 10 lines of code. Or maybe a mini-map

  • @flippert0
    @flippert0 ปีที่แล้ว +1

    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_
      @tri99er_ ปีที่แล้ว

      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.

  • @TheZdannar
    @TheZdannar ปีที่แล้ว

    This video is hot sauce. I had seen Phantom data before, but did not fully comprehend it. Nice work Bogdan.

  • @trashhater9304
    @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

  • @leonardogomes5121
    @leonardogomes5121 9 หลายเดือนก่อน

    Great! Great! Great! The best explanation of the Type State Pattern on TH-cam! You rock man! How can we support your work?

  • @schred
    @schred ปีที่แล้ว +1

    Your best video yet in my opinion, thanks for your work! :D

  • @skyeplus
    @skyeplus ปีที่แล้ว +3

    This example is consuming previous state, which is fine. Can you make a proxy type holding reference instead?

    • @julians.2597
      @julians.2597 9 หลายเดือนก่อน +2

      it intentionally consumes self to prevent dangling references to previous states

  • @andredasilva6807
    @andredasilva6807 ปีที่แล้ว +2

    didnt know about zero types. really nice video. as always keep the great work up

  • @rsalmei
    @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.

  • @minciNashu
    @minciNashu ปีที่แล้ว +10

    Would be interesting to expand on this further by implementing auto lock on drop, raii style.

    • @kajacx
      @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
      @MrTrollland ปีที่แล้ว

      u can just impl the Drop trait cant u

  • @lukasz_kostka
    @lukasz_kostka ปีที่แล้ว

    Fantastic. I love short, to the point tutorials like this.

  • @kwinzman
    @kwinzman ปีที่แล้ว +1

    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!

  • @nirmalyasengupta6883
    @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.

  • @fumseck
    @fumseck ปีที่แล้ว +5

    How feasible would it be to do it the way Mutexes work, with the lock being held only until out of scope ?

    • @inx1819
      @inx1819 ปีที่แล้ว

      this is what I thought the video would be about

    • @Turalcar
      @Turalcar ปีที่แล้ว

      This API prevents calling lock twice while for mutexes calling lock twice (usually from a different thread) is the point. I suppose you could create a Mutex API such that each client gets issued a reference wrapped in a way that prevents lock being callable the second time on the same reference.

  • @goodwish1543
    @goodwish1543 ปีที่แล้ว +2

    Beautiful! Thanks for sharing. Looking forward to more intermediate Rust contents like this. :)

  • @jonnyso1
    @jonnyso1 ปีที่แล้ว

    That's the exact feature I needed the other day and I didn't know yet !

  • @derwintromp1461
    @derwintromp1461 ปีที่แล้ว

    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?

  • @loganhodgsn
    @loganhodgsn ปีที่แล้ว +1

    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)

  • @soberhippie
    @soberhippie ปีที่แล้ว +1

    The problem I ran into is when I try to implement a trait for such state-parameterised struct, I either get "trait not implemented" or "duplicate definitions with the name " errors.

  • @Charls93xx
    @Charls93xx ปีที่แล้ว +1

    That was super straightforward, thanks for sharing!

  • @heret1c385
    @heret1c385 8 หลายเดือนก่อน +1

    great pattern. Man, I just love rust.

  • @CalifornianViking
    @CalifornianViking 6 หลายเดือนก่อน

    Thanks - it is nice to see these concepts being applied.
    Is it possible to achieve this without having to create a new manager when switching from locked to unlocked?

  • @dmdeemer
    @dmdeemer ปีที่แล้ว

    Bogdan,
    Rust neophyte here. I get confused sometimes when my variables get consumed when I don't expect them to be.
    How do you know quickly when a variable is going to be consumed, either by using it as a function parameter, calling an implemented method on it, or even iterating over a vector? Do you just have to be aware of the exact syntax of every function call?
    Also, in the Type-State pattern is there any way to avoid moving each member from one state of the type to the next? That could get tedious for a non-trivial number of states or members.

  • @not_yet_implemented
    @not_yet_implemented ปีที่แล้ว

    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?

  • @b_mathemagical
    @b_mathemagical 8 หลายเดือนก่อน

    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?

  • @nerdbot4446
    @nerdbot4446 ปีที่แล้ว +1

    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.

  • @AntonKravc
    @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.

  • @debuti
    @debuti ปีที่แล้ว

    I think this is maybe your best video

  • @Jiftoo
    @Jiftoo ปีที่แล้ว

    I use this pattern for data sanitisation in my backend. Works great!

  • @nocodenoblunder6672
    @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

  • @mmssus
    @mmssus ปีที่แล้ว

    Question here though, why PasswordManager implementation for the constructor doesn't need a generic like the common methods implementation "encryption" and "version"?

  • @Chastor97
    @Chastor97 ปีที่แล้ว

    Till the end I have been waiting for raii cause I think it fits here well

  • @suya1671
    @suya1671 ปีที่แล้ว +3

    How did you get your "todo!()"s highlighted like that? Looks really helpful.

    • @ex0ze
      @ex0ze ปีที่แล้ว +1

      Seems like TODO Highlight vscode extension

    • @letsgetrusty
      @letsgetrusty  ปีที่แล้ว +9

      The todo-tree VSCode extension + adding this line to settings.json:
      "todo-tree.regex.regex": "(//|#|

    • @suya1671
      @suya1671 ปีที่แล้ว

      @@letsgetrusty TY

  • @antoniong4380
    @antoniong4380 ปีที่แล้ว +1

    Wtf. You can actually do this? So much potential! And head wacking because I need to make sense of the structure

  • @r31527
    @r31527 ปีที่แล้ว +4

    Awesome! Is there a way to prevent users from passing a random zero-sized type that we don't handle?

    • @sailormouse4749
      @sailormouse4749 ปีที่แล้ว +13

      You can define a sealed trait in your library and then implement it for only the state structs you want. Then you can do and now only the types that you wanted the user to pass in as generic can be passed in.

    • @r31527
      @r31527 ปีที่แล้ว +1

      @@sailormouse4749 Thank you so much

    • @juan0338
      @juan0338 ปีที่แล้ว

      Would an enum work?

    • @rez188
      @rez188 ปีที่แล้ว +2

      If you try it yourself, you'll see that there is no way to create an 'Account' struct without using new. new() always returns an 'Account' and the only way to get a different type is by logging in or out.

    • @r31527
      @r31527 ปีที่แล้ว

      @@rez188 True! Completely looked past that. But that's still just in the context of this specific password manager example

  • @shufflecat3334
    @shufflecat3334 ปีที่แล้ว +1

    So how do we handle the case where the client gets the password wrong? The unlock method should either return an unlocked manager or a locked manager...or panic I guess?

  • @giorgos-4515
    @giorgos-4515 หลายเดือนก่อน

    the example is so fitting

  • @Tferdz
    @Tferdz ปีที่แล้ว +8

    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
      @RoyalVideoPresents ปีที่แล้ว

      Why? If you rename the struct shouldn‘t the IDE rename that too?

    • @DavidM_603
      @DavidM_603 ปีที่แล้ว +4

      lock() and unlock() do not return Self, they change the type

  • @Echiduna
    @Echiduna ปีที่แล้ว

    Enjoy this video so much!

  • @jaumesinglavalls5486
    @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?)

  • @wumwum42
    @wumwum42 ปีที่แล้ว

    I think in the final solution, you can merge the first and 4th impl. PasswordManager and PasswordManager is the same

    • @rad9587
      @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)

  • @not_yet_implemented
    @not_yet_implemented ปีที่แล้ว +1

    Interesting pattern! My default is to revert to state variables, usually enums.

  • @jma42
    @jma42 ปีที่แล้ว

    thanks for this, I really wondered how typestate pattern works since it was said that it only works well with rust..

  • @cameronhunt5967
    @cameronhunt5967 ปีที่แล้ว

    Does the compiler help make sure you handle all possible states for structs as it does in match statements?

  • @ThePythonicCow
    @ThePythonicCow ปีที่แล้ว

    Starting at the 2:06 mark I think you accidentally got backwards whether the lock should be held or not when calling list and add password.

  • @codeman99-dev
    @codeman99-dev ปีที่แล้ว

    What's the advantage over just using an enumeration of the password manager?

  • @martinbecker1069
    @martinbecker1069 ปีที่แล้ว +3

    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
      @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.

    • @LukasCobbler
      @LukasCobbler ปีที่แล้ว

      @@julytikh can you explain how not to use the phantom data in this example

    • @julytikh
      @julytikh ปีที่แล้ว +1

      @@LukasCobbler just use `State` instead of `std::marker::PhantomData`.

  • @chillyvanilly6352
    @chillyvanilly6352 ปีที่แล้ว

    Wow, that was genuinely an awesome vid, thank you!

  • @TadaHrd
    @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.

  • @szotsmiklos8549
    @szotsmiklos8549 ปีที่แล้ว

    What stops users from adding a new impl for any random State? It looks like you can't enforce the state type to be some private trait for Locked/Unlocked? Otherwise I think the approach with different structs are better since it's not possible to add weird variants of PasswordManager that way, and for shared methods you can still have a trait and impl for both locked/unlocked manager

    • @minimalhostage
      @minimalhostage ปีที่แล้ว +1

      the video missed one approach to the generic that solves the issue you mention. Use trait bounds instead of setting State to a default struct.
      create a trait in a private module, impl it on your state structs.
      1. trait Lockable;
      2. impl Lockable for Locked {}
      3. impl Lockable for Unlocked {}
      4. pub struct PasswordManager {…}
      5. pub fn get_password_manager() -> PasswordManager
      you can also use the builder pattern to create a password manager. but as long as you keep your marker traits and marker structs private,there is no way for users to add impls. trait bounds and builders are extremely powerful when you want to control apis.

  • @Matt23488
    @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.

  • @Therzok1
    @Therzok1 ปีที่แล้ว +2

    The lock function returns an Unlocked PasswordManager, instead of a locked one.

  • @Spiun666
    @Spiun666 ปีที่แล้ว

    Given that you consume the original password manager, is it necessary to clone the members of the struct (password list and string)

  • @JohnDoe-ji1zv
    @JohnDoe-ji1zv ปีที่แล้ว

    Nice video, really useful, didn’t know about this pattern and state before. Cheers

  • @miriamramstudio3982
    @miriamramstudio3982 ปีที่แล้ว

    Woow, that was very cool. Great teaching. Thanks

  • @NoahSteckley
    @NoahSteckley ปีที่แล้ว

    Would an enum LockedStatus work well here too?

  • @slippinchillin
    @slippinchillin 10 หลายเดือนก่อน

    I like TypeScript exactly because it provides similar functionality to implement this 👍

  • @dirty-kebab
    @dirty-kebab 10 หลายเดือนก่อน

    I was thinking of the double struct idea before you described the first one. 🤠

    • @dirty-kebab
      @dirty-kebab 10 หลายเดือนก่อน

      Okay that was better than I expected.
      Answer me this....
      Why did it default to locked? I couldn't see any detail mentioning why it knew to default to locked? Because it was higher in the file? I feel like rust is deeper than that right?

    • @sunofabeach9424
      @sunofabeach9424 10 หลายเดือนก่อน

      @@dirty-kebab 8:45

  • @Bonta768
    @Bonta768 ปีที่แล้ว

    Excellent. I really enjoyed learning this one.
    What minimum version of Rust can you use this pattern on?

    • @EvanBoldt
      @EvanBoldt ปีที่แล้ว +2

      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.

  • @tathagataroy5153
    @tathagataroy5153 ปีที่แล้ว

    Why not just use a trait PasswordManager with the common methods and then create LockedPasswordManager and UnlockedPasswordManager to implement Password Manager. What would be the difference between using traits and the phantomdata method? I fail to notice any duplication that may take place in the trait method?
    Thank you.

    • @flippert0
      @flippert0 ปีที่แล้ว

      I did exactly that in the past (see my comment above)

  • @mustafazakiassagaf1757
    @mustafazakiassagaf1757 ปีที่แล้ว

    i like this kind of video. keep up the good work man

  • @unfilledflag
    @unfilledflag ปีที่แล้ว

    I'm not well versed in Rust, so a question: in the last solution in line 66 you're reusing the name `manager`; but suppose you don't, and you just do `let closed_manager = Manager::new ...` then `let open_manager = closed_manager.unlock()`; can you keep acting on closed_manager? Or does this force the user to stop using closed_manager after you unlock it?

    • @WannesVantorre
      @WannesVantorre 10 หลายเดือนก่อน +2

      `closed_manager.unlock()` consumes the instance of `self` so that means once unlock() is done with closed_manager and a unlocked_manager is returned, the closed_manager instance will be deleted. Deleted means it's not usable at all anymore even at compile time, since rust's type system is really good at making sure of this.

    • @WannesVantorre
      @WannesVantorre 10 หลายเดือนก่อน

      Actually it's called "dropping" an object but it seemed easier to explain it using "deleting" as my wording.

  • @rez188
    @rez188 ปีที่แล้ว

    How does it know what the default value of phantomdata will be? Where is it defined that it will default to Locked?

    • @sailormouse4749
      @sailormouse4749 ปีที่แล้ว +1

      It's the "= Locked" in "struct PasswordManager"

  • @irlshrek
    @irlshrek ปีที่แล้ว

    this was a really good explanation!! thanks for this

  • @JUMPINGxxJEFF
    @JUMPINGxxJEFF ปีที่แล้ว +2

    Nicely explained, thanks

  • @setoelkahfi
    @setoelkahfi ปีที่แล้ว

    This been really useful. Thanks!

  • @xavhow
    @xavhow ปีที่แล้ว

    This is so useful!
    Thank you for sharing.

  • @AlwaysStaringSkyward
    @AlwaysStaringSkyward ปีที่แล้ว

    I learned a lot from this. Thank you!

  • @alexandersemionov5790
    @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

  • @thanhatpham3428
    @thanhatpham3428 ปีที่แล้ว

    Thank you, now i know how to do something like inheritance in rust

  • @jay-tbl
    @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?

  • @AceofSpades5757
    @AceofSpades5757 ปีที่แล้ว

    I haven't seen this one before. Very cool!

  • @MrLunarpulse
    @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.

  • @juan0338
    @juan0338 ปีที่แล้ว +1

    Would it be better to use an enum instead of the empty structs?

    • @rez188
      @rez188 ปีที่แล้ว +1

      That wouldn't give you the compile time errors. Using an enum, there is nothing stopping a unlocked account from trying to login

    • @Turalcar
      @Turalcar ปีที่แล้ว +2

      Rust doesn't allow enum const parameters. And even if you use bool there's no specialization by const parameter value.

  • @AveN7ers
    @AveN7ers ปีที่แล้ว +1

    Very cool stuff man

  • @imadetheuniverse4fun
    @imadetheuniverse4fun ปีที่แล้ว +1

    but didn't you also introduce cloning of the entire data whereas the initial "problematic" version didn't need to clone itself each time lock/unlock is called?

    • @drvanon
      @drvanon 6 หลายเดือนก่อน

      That is mainly because of the function parameters. The type of self is a reference. Had he moved self, then he could have moved the password in as well.

  • @IAMSolaara
    @IAMSolaara ปีที่แล้ว +1

    Small question: when declaring the generic State thing you set it to default to Locked, but I saw no “restraint” on what types you could pass it. would it be a good idea to use something like there ‘where’ keyword for functions or is it useless since you dont define impl’s for other types in here? wouldn’t people be able to define other impl’s when they feel like it tho?

    • @kajacx
      @kajacx ปีที่แล้ว

      Yes, people can define their own impls. But that's not really a problem, associated methods are just syntax sugar anyway.

    • @PacificBird
      @PacificBird ปีที่แล้ว

      Actually I think leaving it open is helpful, since users of the API can essentially define new PasswordManagers and extend their own impl blocks if they need to!

    • @tomer4566
      @tomer4566 ปีที่แล้ว

      The struct isn't 'pub' so users can't construct instances with unexpected contents of 'state'.

  • @jeffjia106
    @jeffjia106 ปีที่แล้ว

    can we make Locked, Unlocked from struct to enum?

  • @ianknowles
    @ianknowles ปีที่แล้ว

    Always handy Bogdan appreciate the share!

  • @MiterAlmeida
    @MiterAlmeida ปีที่แล้ว

    Loved this video! ❤

  • @khai96x
    @khai96x ปีที่แล้ว +3

    Locked and Unlocked are already zero-sized, why PhantomData?

    • @laurentle-hebrard544
      @laurentle-hebrard544 ปีที่แล้ว

      It gets rid of the "unused" error on the parameter State.

    • @maow-tty-archive
      @maow-tty-archive ปีที่แล้ว

      @@laurentle-hebrard544 It's a bit more complicated than that. There's a good, albeit technical explanation of PhantomData's purpose in the Rustonomicon.
      The easiest way I can explain it is that it allows the compiler to properly handle the type parameter.

    • @khai96x
      @khai96x ปีที่แล้ว +3

      @@maow-tty-archive You can still use Locked and Unlocked directly, and the compiler would still distinguish PasswordManager and PasswordManager.
      PhantomData is only necessary when either:
      1. Locked and Unlocked aren't zero-sized, which isn't the case here.
      2. Locked and Unlocked cannot be initialized, which also isn't the case here.

    • @maow-tty-archive
      @maow-tty-archive ปีที่แล้ว

      @@khai96x Ah okay. I guess my only argument for it here would be... it helps highlight the purpose of the type argument in a self-explanatory way? I dunno, just a stretch to make some sense out of that decision. In the context of a tutorial, it's not that bad of a usage, since it helps highlight why someone would want to use PhantomData.