Rust Programming: TypeState Builder Pattern Explained

Поділитися
Вставка
  • Опубліковано 17 січ 2023
  • Rust Programming Tutorial for the TypeState builder pattern. We will also learn how to use PhantomData as one of the states.
    Help support this channel ► / jeremychone
    Previous Video on builder pattern: • Rust Programming: The ...
    GitHub source (with the other builder patterns): github.com/jeremychone-channe...
    Some Notes:
    1) PhantomData are erased by the compiler, meaning they do not exist at "runtime" but allow code to use them to leverage the compiler's type system.
    2) As commented by 'Harold Ruiter,' the 'build()' method has become infallible, meaning it cannot fail, and therefore, it can just return 'Request', rather than Result of Request.
    3) The TypeState pattern does not have to be used only with the builder pattern.
    4) As mentioned in the video, generics increase the binary size and compile time. While, more often than not, generics' value outweighs those costs, there is still a point of diminishing return if generics are overused.
    5) To follow the #4 point, in this tutorial, we showed the multi states pattern with URL and Method, but designing a builder with too many states, for each property, for example, would be overkill and have counter-productive side effects.
    6) In theory, the pure definition of the TypeState pattern assumes the same type is returned, which is technically not feasible in Rust, as Generic creates a different type. However, from a practical Rust perspective, this is a good representation of the TypeState pattern.
    Thanks to Sergey Potapov's great blog post on "Builder with typestate in Rust" (www.greyblake.com/blog/builde...)
    Jeremy Chone:
    - Twitter - / jeremychone
    - Discord On Rust - / discord
    - AWESOME-APP - awesomeapp.dev - Rust Templates for building awesome applications.
    - Patreon - / jeremychone - Any help is a big help (for Rust educational content)
    Other popular Rust Programming videos:
    - Rust Builder Pattern - • Rust Programming: The ...
    - Quick Start Code Layout - • Rust - Simple Code Lay...
    - AWESOME-APP Full Overview - Rust template for building Awesome Desktop Application: • Building Awesome Deskt...
    - Tauri Quick Introduction (Desktop App wit Rust Programming): • Rust Tauri 1.0 - Quick...
    - Rust Web App tutorials series: • Rust Web App - 1/3 - D...
    - Rust Bevy Full Tutorial - Game Development with Rust: • Rust Bevy Full Tutoria...
    - Rust for Java Developers series: • Rust for Java Develope...
    Playlists:
    - Rust For Desktop App: • Rust Programming for D...
    - Everything Rust Programming - Tutorials, Courses, Tips, Examples: • Everything Rust Progra...
    - Rust Programming for Web Development: • Rust Programming for W...
    - Rust Courses: • Rust Course 2021 by th...
    - Rust for Java Developers: • Rust for Java Developers
    Other notes:
    - Tool used to do the green lines. ScreenBrush on Mac App Store (Gromit seems to be the equivalent on Linux)
    - Edited with Davinci Resolve.
  • Наука та технологія

КОМЕНТАРІ • 66

  • @hojjat5000
    @hojjat5000 Рік тому +46

    I appreciate the amount of work you put into your videos.

  • @HaroldR
    @HaroldR Рік тому +23

    Doesn't build() become infallible? You should be able to omit Result and just make it return T

    • @JeremyChone
      @JeremyChone  Рік тому +14

      Oops, that is correct. I missed this one somehow. Thanks for the note. (I will add it as a description note).

  • @meowsqueak
    @meowsqueak 9 місяців тому +12

    This, and the previous video, are excellent - thank you. I'd love to see more of these design-focused videos from you, please. There are many Rust beginner/syntax videos, but a video on this kind of topic, where the strengths of the language are used to build useful software, is quite rare.

  • @flyaruu
    @flyaruu Рік тому +48

    Nice one, I'm slowly getting used to the enormous number of generic types in Rust. One downside I see is that the compile time errors aren't as clear as the runtime errors. "method 'build' not found in RequestBuilder" isn't the clearest way to communicate that the consumer hasn't called the url method. Do you know of ways to improve that?

    • @JeremyChone
      @JeremyChone  Рік тому +15

      Good point. Unfortunately, beside naming your state as clearly as possible, I do not think there is a way around that.

    • @LimitedWard
      @LimitedWard Рік тому +12

      I agree, it does definitely make it a bit harder to read the error messages. I think the best workaround would be to give descriptive names for your type state structs. Instead of "NoUrl" you could use "MissingUrl" or "WithoutUrl". Then the error message would read "method not found in `RequestBuilder`". The word "no" feels more neutral whereas "missing" or "without" imply something is not present when it should be.

  • @ankar71
    @ankar71 Рік тому +13

    So, in effect, you have some kind of Design by Contract in compile time. You can design an API in a way that it is impossible to have structures in an inconsistent state and the only checks that will remain in runtime will be those that are impossible to know at compile time like validation checks and runtime errors which in Rust you are forced to handle anyway because they are behind Option or Result.

    • @JeremyChone
      @JeremyChone  Рік тому +8

      Yes, this is the gist of it. It comes at a cost but can be worthwhile in some scenarios.

  • @agustindiaz3361
    @agustindiaz3361 Рік тому +11

    Awesome! Really powerful the way you use generics and you are allowed to compose them independently

  • @seblund
    @seblund Рік тому +11

    Exactly what I was thinking when i requested it. Appreciate the quick turnaround time!

    • @JeremyChone
      @JeremyChone  Рік тому +3

      Thanks for the request. In a way, it is good it is in its own video. There is quite a bit of information.

  • @learnityourself
    @learnityourself Рік тому +6

    Haven't got much time lately but whenever I see your video Jeremy its always delight. Happy new year. I know your channel is going to explode this year.

    • @JeremyChone
      @JeremyChone  Рік тому

      Thank you for this super nice comment. Trying to produce more without altering quality.

  • @nirmalyasengupta6883
    @nirmalyasengupta6883 8 місяців тому +2

    Excellent video! Thanks. I have been making sincere attempt to build all applications/components by being _Type-driven_ ! I have been reasonably successful in Scala and Java, as well! I am trying to get used to similar direction in thinking about the _Types_ in Rust too. This video explains the approach very lucidly! 👍

  • @jorgeosorio1613
    @jorgeosorio1613 Рік тому +5

    Thank you for taking the time to make these videos, they are very helpful.

  • @KayOScode
    @KayOScode 5 місяців тому +2

    Very interesting. It does make me wonder if this is something you’d even want to do since there’s a lot of boilerplate involved. Due to the number of datapoint wise moves, for bigger structures it could get annoying to add new fields. But it’s still really cool

    • @JeremyChone
      @JeremyChone  5 місяців тому

      Yes, agree. This is not to be abused.

  • @michaelheinrich5219
    @michaelheinrich5219 Рік тому +4

    Insightful and easily adaptable for my own projects, thanks!

  • @FreshLlamanade
    @FreshLlamanade Рік тому +4

    Wow, Rust makes this pattern really elegant to implement compared to other languages like Java! Great video, I subbed :)

  •  6 місяців тому

    That video was awesome. Brilliantly depicted the mechanism. Easy to follow. Love it

  • @hojjat5000
    @hojjat5000 Рік тому +5

    Very clear and very useful. Great video.

  • @yobenezneb2
    @yobenezneb2 9 місяців тому

    Thanks, this was tremendously helpful and made it very easy to recontextualize to my own project. Really enjoy this channel for intermediate Rust concepts!

  • @vsuryabhargava
    @vsuryabhargava Рік тому +1

    Thank you Jeremy, this was a great video and I'm glad youtube put this in my feed.

  • @rsalmei
    @rsalmei 3 місяці тому +1

    This is the best typestate video I've seen, thank you!

  • @parthmittal5625
    @parthmittal5625 8 місяців тому +1

    Fantastic video, cleared all my confusion!

  • @el_carbonara
    @el_carbonara Рік тому

    makes me motivated to keep learning rust as the compile time checks are powerful.

  • @irlshrek
    @irlshrek Рік тому +2

    yessss this is MUCH better than the builder pattern!! thank you for this

  • @shashanksharma21
    @shashanksharma21 9 місяців тому +1

    Very nicely explained ! Thank you!

  • @jocketf3083
    @jocketf3083 Рік тому

    Excellent example and explanation! Thanks!

  • @crazyingenieur3277
    @crazyingenieur3277 3 місяці тому +1

    I am simply amazed!!!

  • @volking1
    @volking1 9 місяців тому +1

    Excellent video, thank you!

  • @12e2aela7
    @12e2aela7 6 місяців тому

    Thank you so much for the great content. You are an awesome teacher.
    Greetings from Iran :)

  • @cheebadigga4092
    @cheebadigga4092 6 місяців тому +1

    Awesome!! Thank you!

  • @obichinedufrank
    @obichinedufrank Рік тому

    Really amazing video. Thank you

  • @FabianVilersBe
    @FabianVilersBe Рік тому +6

    Nice pattern but if some values are mandatory why not have them as parameters to the new function?

    • @JeremyChone
      @JeremyChone  Рік тому +6

      You are correct. A constructor for the required props and returning a simpler builder for the optional ones is also a good approach. It depends on the interface that makes the most sense for the particular case. But starting simple is always a good first approach.

  • @bobby9568
    @bobby9568 Рік тому

    Amazing channel

  • @alexandersemionov5790
    @alexandersemionov5790 Рік тому

    Outstanding!

  • @JeffHanke
    @JeffHanke Рік тому

    Very well explained, but a very confusing topic! I'm curious to see peoples questions.

  • @CuriousSpy
    @CuriousSpy Рік тому +2

    It is time to do new video brother! You are my favourite! :D

    • @JeremyChone
      @JeremyChone  Рік тому +1

      Yes, working on a big one as we speak! It will probably be next Sunday. Full intro on Axum.
      Also, I made some arrangements to focus more on those videos and awesomeapp.org for the rest of the year and more if it works well.

    • @CuriousSpy
      @CuriousSpy Рік тому +1

      @@JeremyChone wow, I'm excited!

  • @Turalcar
    @Turalcar 5 місяців тому

    I implemented this pattern several years ago for some generated code and only recently found out what it's called. It was in C++ so I used a bitmask for mandatory fields

  • @HyperFocusMarshmallow
    @HyperFocusMarshmallow Рік тому

    Does the size of the types have any impact on binary size or is the overhead mostly related to the amount of types?

    • @JeremyChone
      @JeremyChone  Рік тому +1

      Good question. Assuming the code uses all of those variants, it will impact binary size. The compiler "monomorphize" the generics (create another type/function for the used generic type)..
      Same when you use "impl Into" in a function argument. The compiler will create a duplicate function for each used concrete type.

  • @BrandonDyer64
    @BrandonDyer64 22 дні тому +2

    I believe you can use the unit type `()` instead of `NoUrl` and `NoMethod`. So have ::new() return a `RequestBuilder`.

    • @JeremyChone
      @JeremyChone  22 дні тому

      Good point. I did not test it, but I think it should work and could be a valid approach.

  • @oDHAOSo
    @oDHAOSo Рік тому +2

    I hope there's a crate that does all this work without taking up space in my own code (maybe using macros?). I love the ergonomics of using the builder struct, but I'd dread having to go back into the builder code and add or remove a field in 6 months.

    • @JeremyChone
      @JeremyChone  Рік тому +3

      Not that I know of. Also, not sure a crate will add much value.
      These methods need to be implemented within the right scope (i.e., types/generics), so I am not sure external macros or types will help make the code more readable and easier to maintain. It might have the opposite effect.

  • @markk3156
    @markk3156 Рік тому +1

    Really great example. I can imagine JWT might be a situation where you don't want builder methods called after it is "sealed". How does #[must_use] fit into this pattern? Can you do a real world video on #[must_use] ?

    • @JeremyChone
      @JeremyChone  Рік тому

      Thanks.
      `#[must_use]` is a nightly only, right?

    • @valshaped
      @valshaped 9 місяців тому +1

      ​@@JeremyChone `#[must_use]` as an attribute on types, traits, and functions is stable. `std::hint::must_use(...)` is unstable.

  • @busydying
    @busydying Рік тому +5

    The biggest downside of the approach from my perspective is inability of conditional building. E.g. *let mut b = Builder::new(); if true { b = b.url(); }* won't compile.

    • @JeremyChone
      @JeremyChone  Рік тому +1

      This is a very good point.

    • @mikkelens
      @mikkelens Рік тому +3

      That's the price you pay by using if blocks to manage state. If statements can never make a promise about what they represent (unlike match), so they cannot and should not be thought of as usable in type safe contexts like this pattern enforces.

    • @busydying
      @busydying Рік тому

      @@mikkelens Do you see how `match` would help here?

  • @anridev24
    @anridev24 Рік тому +3

    You are amazing, How can we support you?

    • @JeremyChone
      @JeremyChone  Рік тому +2

      Thank you for the encouragement. For now, just sharing those videos will help a lot.
      I typically like to do the Rust subreddit sharing myself (usually a week later), but if viewers can share the video(s) on other social and coding platforms, that is always welcome. No pressure though, share the ones you really like.
      Thanks for asking and happy coding!

  • @cunningham.s_law
    @cunningham.s_law 24 дні тому

    how do you feel about the derive_builder crate?

    • @JeremyChone
      @JeremyChone  13 днів тому

      I am not a big fan (yet) of those derive builder libraries.
      They can remove some boilerplate, but they enforce practices that are sometimes unnecessary. I find myself spending more time figuring out how to opt-in/out of those features rather than just coding in the style I want.
      For example, one of those derive macros has the .build() returning a Result, but sometimes I don't need that; I want them to be infallible.
      Additionally, in some cases, like in my new GenAI library, I have `with_...` setters in the builder style, but I don't need a .build() as those can just work on the target type.
      Typically, for my APIs, I like to design sometimes with_... (setter builder style), from_... (constructors), new(..) (default constructors with the most common data), and default() for infallible default constructors.
      Not worrying about learning those macro library notations and just focusing on the style I want helps me prioritize user ergonomics. Later, I might remove some of the boilerplate behind the scenes, but that is not the priority.

  • @richardpayne
    @richardpayne 5 місяців тому

    What's the benefit of this setup over just not deriving Default and setting any default values you might want in the new function? The new function must be supplied with a url, and if someone happens to instantiate the struct directly, they must provide a value for url anyway?

    • @JeremyChone
      @JeremyChone  5 місяців тому

      Ah, that can be a strategy. And the "Default" here is not part of the pattern.
      The point of this pattern is that these are technically different types, so the first type won't even have the other functions, making it compile/type proof.
      However, this should not be abused, as it can get quite complicated at times.
      So, it's a good pattern to know, but not necessarily one to use often.

  • @michaelzap8528
    @michaelzap8528 8 місяців тому

    Very confused. I figured it out two weeks later.it turned to be function programming style, if u think of it this way, it's super easy to understand it.

  • @martinfrances107
    @martinfrances107 Рік тому

    I think this is a good idea.... here is a different video showing the same idea ua-cam.com/video/bnnacleqg6k/v-deo.html