The magic of Rust's type system

Поділитися
Вставка
  • Опубліковано 20 бер 2024
  • For today's video, we dissect a tangled mess and uncover its pitfalls. Let's see how Rust's type system empowers developers to enforce crucial invariants, paving the way for cleaner, safer and more robust software.
    Free Rust cheat sheet: letsgetrusty.com/cheatsheet
  • Наука та технологія

КОМЕНТАРІ • 171

  • @letsgetrusty
    @letsgetrusty  2 місяці тому +10

    📝Get your *FREE Rust cheat sheet* :
    letsgetrusty.com/cheatsheet

  • @AndrewBrownK
    @AndrewBrownK 2 місяці тому +164

    I haven't heard of "parse, don't validate" before, but I love it

    • @SharunKumar
      @SharunKumar 2 місяці тому +1

      Isn't that the zod motto

    • @Randych
      @Randych 2 місяці тому +2

      It has nothing to do with rust, it's haskell thing

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

      @@RandychI mean it's born from Haskell ecosystem's contributor - Lexi Lambda, but tbh applies to any kind of language with a decent type system. I love popularization of this short quote

    • @Randych
      @Randych 2 місяці тому +2

      @@sealoftime yes that's what I actually wanted to say

  • @ya3rub101
    @ya3rub101 2 місяці тому +90

    this aligns perfectly with one of most important software design principles: Make INVALID state UN-REPRESENTABLE.

    • @svenyboyyt2304
      @svenyboyyt2304 2 місяці тому +1

      Do you mean unrepresentable?

    • @ya3rub101
      @ya3rub101 2 місяці тому

      @@svenyboyyt2304 Oh, sorry for that. edited.. thnx.

    • @LageAfonso
      @LageAfonso 2 місяці тому +3

      Noboilerplate made a video about this

    • @ya3rub101
      @ya3rub101 2 місяці тому

      @@LageAfonso yah, but you can try to apply this using any programming language, some languages help you a lot doing this like rust,C#,typescript.. unlike Go where you should do hacky things to be even close to achieve that.
      this may help someone: one of my favorite resources in this topic for typescript is khalilstemmler article : Make Illegal States Unrepresentable! - Domain-Driven Design TypeScript.

  • @askholia
    @askholia 2 місяці тому +47

    This is the greatest start of a video on any language I have seen. Also...I use Arch, btw. Sic Semper Tyranus!

    • @letsgetrusty
      @letsgetrusty  2 місяці тому +7

      Not the brag, but I'm proficient in Scratch btw

  • @ruanluies5909
    @ruanluies5909 2 місяці тому +50

    6:32 prase -> parse

  • @christopher8641
    @christopher8641 2 місяці тому +3

    You should include how your request handler can perform the deserialization for you with serde. With that, you can be certain that the handler itself never even accepts an invalid request. Function signatures are one of the strongest forms of documentation

  • @linkernick5379
    @linkernick5379 2 місяці тому +3

    This jumped on the new level in terms of the video effects and presentation. Wonderful!

  • @scott6572
    @scott6572 2 місяці тому +2

    Very nice, this feel a lot like "opaque types"

  • @Turalcar
    @Turalcar 2 місяці тому +7

    A great example is [u8] and Vec vs str and String: the only difference is the invariant.

    • @TheEvertw
      @TheEvertw 2 місяці тому

      str is NOT a simple array of bytes. str contains characters that support the full Unicode range. To store a Unicode character, one needs at least 3 bytes. Many encodings exist, some of which try to maintain some sort of backward compatibility with good 'ole ASCII, like UTF-8. There is a format conversion from bytes to string and back, which converts e.g. UTF-8 encoded binary data into the Unicode representation used by Rust internally. I don't know for sure, but I wouldn't be surprised if Unicode characters are represented simply as u32 inside Rust.

    • @Turalcar
      @Turalcar 2 місяці тому +2

      @@TheEvertw str can be safely transmuted to [u8] and back (but it's UB if the sequence of bytes is not valid UTF8). char is indeed 4 bytes though.

    • @TheEvertw
      @TheEvertw 2 місяці тому

      @@Turalcar True, but as the different type implies, they are fundamentally different things. [u8] is an array of bytes that are an UTF-8-encoded representation of the str. That means it knows to skip the unused parts of an u32 Char, and that s.len() can be smaller than a.len().

    • @Turalcar
      @Turalcar 2 місяці тому

      @@TheEvertw They are different types solely due to invariants

    • @Turalcar
      @Turalcar 2 місяці тому

      @@TheEvertw Also, s.len() is length in bytes

  • @Heater-v1.0.0
    @Heater-v1.0.0 2 місяці тому +2

    Excellent. I was just settling down to find out how this Rust "type state" idea I had heard of works and here you are explaining it so clearly, right on cue. Thank you.

  • @VictorGamerLOL
    @VictorGamerLOL 2 місяці тому +6

    checked_sub is also useful to make that withdraw function.

  • @mintx1720
    @mintx1720 2 місяці тому +27

    My account has NaN money.

    • @culturedgator
      @culturedgator 2 місяці тому +2

      My account is an imaginary number

    • @Gruak7
      @Gruak7 2 місяці тому

      My account balance is of the i8 type.

    • @vaibhavbv3409
      @vaibhavbv3409 2 місяці тому +2

      Speed: null
      Altitude: undefined
      Inclination: NaN
      Fuel: null
      Oxidizer: undefined
      Status: OK

    • @soushi8885
      @soushi8885 2 місяці тому +1

      @@Gruak7 This is sad.

    • @hickscorp
      @hickscorp 2 місяці тому

      Well NaN might be plural, so it has NaN moneys - I **think**?

  • @ariaden
    @ariaden 2 місяці тому +39

    Whoa, I bet that change from i32 to u32 makes the code riskier financially.

    • @carlosmspk
      @carlosmspk 2 місяці тому +3

      yeah, I think it wasn't the best example

    • @torinfaes6278
      @torinfaes6278 2 місяці тому +1

      At least rust has runtime checks to prevent overflow right?

    • @ianknowles
      @ianknowles 2 місяці тому +9

      Yup I ussually get good tit bits from creators like LGR - he does a great job of condensing ideas and digesting new features. But i32 to u32. I feel like I could make 4.3billion on quite easily ;)... It's very dangerous

    • @s1v7
      @s1v7 2 місяці тому +1

      i see, that it could make calculations not straightforward, as you cannot use + and - without implementing custom `add` and `sub` methods.
      and you cannot have an account with debt.
      but why it makes code riskier? i did not get.

    • @singularity1130
      @singularity1130 2 місяці тому +2

      @@s1v7 I know in C/C++ if you subtract 1 from 0 that's a unsigned int it wraps around to the largest possible number but in Rust I'm sure it simply panics saying that's not allowed unless I'm missing something.

  • @prabeshsenpai5396
    @prabeshsenpai5396 2 місяці тому +3

    I love these kinds of videos, please make more videos with logical errors and it's fix with code examples

  • @papadavis47
    @papadavis47 2 місяці тому

    Well done 💯 The production value and editing on your videos continues to get better and better. This was a good one. It is appreciated.

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

    Bohdan, thank you for the new video. A very interesting point was made in the second part regarding Struct - it looks quite intriguing. I have only two comments: 1) If it's merely a trick example, no problem - it's interesting. However, if the goal was to demonstrate its usage in real applications, I'd like to advise newcomers that in real applications, it's more about meta-programming, and Roles should be at least enums. 2) An "empty" struct() doesn't cost anything, hence there's no need to use PhantomData; PhantomData serves another purpose, as demonstrated in the example.

    • @svenyboyyt2304
      @svenyboyyt2304 2 місяці тому

      I think the reason User isn't an enum is because all the data is the same so it's redundant to repeat it. And I think the reason why he didn't use a field with an enum is because the variant is known and doesn't need to be stored like an enum. That makes it less flexible than an enum because you have to know which one it is but it takes up less memory and means you can also give them all different implementations, like how only the editor even has the edit method. But yes, newcomers shouldn't use this.
      You don't need an empty tuple struct like this "struct X();", it could also be a normal empty struct like this "struct X {}" or like how he did it like this "struct X;", which is similar. So the only thing you would have to do is remove the PhantomData wrapper.

  • @user-uc6wo1lc7t
    @user-uc6wo1lc7t 2 місяці тому +1

    Didn't know generics can be used like this in structs 😮
    Thank you!

  • @valhalla_dev
    @valhalla_dev 2 місяці тому +3

    > bank account cannot be less than zero
    damn I wish

  • @t4t5
    @t4t5 2 місяці тому

    Great video! 🙌 Would love more like this where you go through best practices with examples

  • @mistamunsta
    @mistamunsta 2 місяці тому +14

    Careful that the rust foundation doesn't get pissy bout that thumbnail

    • @letsgetrusty
      @letsgetrusty  2 місяці тому +6

      Life is too short to play it safe ;)

  • @artxiom
    @artxiom 2 місяці тому +1

    Very interesting video! To be honest I have been programming like that since around two decades or so in C++ but there you are more limited by the type system how much you can enforce certain invariants. Great to see the power and flexibility of Rust's type system!

  • @Simple_OG
    @Simple_OG 2 місяці тому +175

    I use arch btw

    • @maximus1172
      @maximus1172 2 місяці тому +34

      I use nix btw

    • @nhieljeff2156
      @nhieljeff2156 2 місяці тому +9

      do you even vim?

    • @AdamFiregate
      @AdamFiregate 2 місяці тому +11

      I use Windows 11 + WSL Ubuntu + iOS. 🌞💛

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

      You should rewrite Arch in Rust...

    • @maninalift
      @maninalift 2 місяці тому

      ​​@@maximus1172 I use nixos with my own hydra server btw
      (Actually, I don't, I have a pretty janky all in one file nixos config and I don't run my own hydra)

  • @iflyplanesthrutunnels
    @iflyplanesthrutunnels 2 місяці тому

    the fact that the first thing said in this video is "this code is a pile of sh-[BLEEP]" cracked me up

  • @KamyarInanloo
    @KamyarInanloo 2 місяці тому

    We can also use a couple of other methods in some languages: 1. Design by contract 2. Inductive types

  • @AlexEscalante
    @AlexEscalante 2 місяці тому

    Your videos are always great, this in particular is useful for other languages as well. Good work!

  • @LageAfonso
    @LageAfonso 2 місяці тому

    Nice, I would love to see more videos about type-driven design

  • @meka4996
    @meka4996 2 місяці тому

    Very helpful! Thank you.

  • @maltamo
    @maltamo 2 місяці тому

    Really liked this video, would love to hear more about good practice in rust.

  • @chillyvanilly6352
    @chillyvanilly6352 2 місяці тому +3

    great vid!
    07:18 => yet I would very much like to see a follow-up vid regarding this `PhantomData` what its uses are (beyond the Rust book if there is info available) and what did you mean exactly with "... to avoid unnecessary allocation."?

    • @KPidS
      @KPidS 2 місяці тому

      Look up "Improve your Rust APIs with the type state pattern" on this channel

    • @chillyvanilly6352
      @chillyvanilly6352 2 місяці тому

      @@KPidS nah... didn't get into that...I've only gotten lucky in the Rust Nomicon docs

  • @KPidS
    @KPidS 2 місяці тому +1

    6:15 When you need to parse a struct from string you should implement a FromStr trait instead. If you impelement FromStr, you can then do string.parse::(), like you can do with numbers string.parse::(). Similar to how you should implement From trait for conversion between other types, because you also get Into for free.

    • @busydying
      @busydying 2 місяці тому

      There'a also `AsRef` - the question is whether it worth using them in videos meant for not-yet-rustaceans, as it adds complexity of the trait idiom, arguably obfuscating the main idea

    • @busydying
      @busydying 2 місяці тому

      Also FromStr imply unnecessary allocation for copying the input string which is better to be consumed to also avoid the use afterwards by mistake, so I'd argue for just `fn new(s: String) -> Result`

  • @fabricehategekimana5350
    @fabricehategekimana5350 2 місяці тому

    Thanks for your high quality video !

  • @erlangparasu6339
    @erlangparasu6339 2 місяці тому

    Thanks! Best explanation as always

  • @Felipe-53
    @Felipe-53 2 місяці тому

    Wow, great video! Thank you

  • @EddieCheng174
    @EddieCheng174 2 місяці тому

    Great video. Thanks!

  • @AdamFiregate
    @AdamFiregate 2 місяці тому

    Thank you, Bogdan. ✨🌞

  • @RobertKing
    @RobertKing 2 місяці тому

    really great video. Rusts type system is so beautiful. Can I cask how did you make the video? very beautiful animations.

  • @dr.med.janschiefer7163
    @dr.med.janschiefer7163 2 місяці тому +2

    Very nice video.

  • @HumanoidTyphoon91
    @HumanoidTyphoon91 2 місяці тому

    Thank you for the video. Most of these principles can be used in most languages, it's a shame that they aren't popular amongst other developers.

  • @eric-id6bk
    @eric-id6bk 2 місяці тому

    Awesome video man ❤

  • @scott3489
    @scott3489 2 місяці тому +2

    Excellent

  • @chrissalgaj4111
    @chrissalgaj4111 2 місяці тому +1

    Where is that style guide from?

  • @MadMathMike
    @MadMathMike Місяць тому

    I love Rust, and I really like this channel, but I actually think these patterns are pretty easy to implement in other languages too. I don't see Rust having a particularly big advantage where these patterns are concerned. Most people who have learned about DDD and some iteration of "clean" architecture will have likely seen how these patterns can be implemented in their language.

  • @armanmirk
    @armanmirk 2 місяці тому

    This concept/pattern is also more commonly known as "making impossible states impossible" (to represent so to speak).

  • @WolfrostWasTaken
    @WolfrostWasTaken 2 місяці тому

    This can be implemented in any language, but Rust makes it so simple!

  • @macaco_agiota
    @macaco_agiota 2 місяці тому

    Awesome video!

  • @max_masterius
    @max_masterius 2 місяці тому

    One, for me rust is web dev is so interesting topic. I've just started rust but I have strong background on web development with dynamic typing language. We use validators mostly and a lot of tests to ensure everything is fine.

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

    I think the second half on the newtype pattern and parse don’t validate is much stronger than the opening using unsigned arithmetic. Having an always positive bank balance is not realistic, overdrafts happen, and just happens to align with an available number type. Adding parsing functions is much more flexible and future-proof. The validity of data is a separate concern from its representation.

    • @svenyboyyt2304
      @svenyboyyt2304 2 місяці тому +2

      It's just an example. If it's realistic or not doesn't matter. And he specifically said no overdrafts because it's to demonstrate a point.

  • @python-for-everyone
    @python-for-everyone 2 місяці тому

    Thank you for this great tutorial. I noticed one thing in your code. After switching to the Email and Password structs, you call the parse methods (6:30) but do not return a BadRequest anymore. What happens when Email::parse fails? I have no experience with rust and wonder what information the email and password structs actually contain when the validation failed.

  • @raidensama1511
    @raidensama1511 2 місяці тому +1

    @letsgetrusty (aka Bohdon) how about a video with details on using the From,TryFrom and AsRef traits.

  • @wiiznokes2237
    @wiiznokes2237 2 місяці тому +1

    Cool, I had never seen an use case phantom data before

    • @Choose52
      @Choose52 2 місяці тому

      You’re right, it’s not often used. I’ve seen it used mainly in embedded systems contexts, such as hardware drivers.

    • @oneofpro
      @oneofpro 2 місяці тому

      Same thing, but I advise not to use it like in the example, because the default structure, if empty, does not consume memory at all. And PhantomData exists for absolutely other purpose.

  • @MateoRodriguez-ch3ek
    @MateoRodriguez-ch3ek 2 місяці тому

    Question: why do you use PhantomData on a zero sized struct? I thought the optimizer would not waste padding on a type that has no size.

  • @josuebarreto7687
    @josuebarreto7687 2 місяці тому

    meanwhile javascript devs: wait types exist?

  • @mira_nekosi
    @mira_nekosi 2 місяці тому

    when refinement types in rust?

  • @konstanti
    @konstanti 2 місяці тому +1

    Why you didn't use the FromStr trait?

  • @aaaronme
    @aaaronme 2 місяці тому

    A big problem i have is coditional structs, like if type is `declined` that there is an `error` and if it's `accepted` that there is a `message`. This is possible trough enums, yes, but not so when receiving a json response from a 3rd party API.

    • @christopher8641
      @christopher8641 2 місяці тому

      Resut

    • @aaaronme
      @aaaronme 2 місяці тому

      @@christopher8641 So can I do Result ? So it will try all of them first. That would be great 🤣

    • @christopher8641
      @christopher8641 2 місяці тому

      @@aaaronme that is a sum type. As in, the type is the sum of all possible types. You should use an enum as the T in Result . Each enum variant is a newtype wrapper around a possible response from the API you are calling. I believe you would use an #[serde(untagged)] on the enum so serde tries all possible variants

    • @aaaronme
      @aaaronme 2 місяці тому

      @@christopher8641 Do you have a link for a documentation about this? I have actually been struggling with inconsistent API responses and currently just resulted in making almost every field Optional and one time I am trying to serialize it to StructA, if it fails, I know it's StructB,

  • @handsanitizer2457
    @handsanitizer2457 Місяць тому

    Can you make an updated rust best practices videos 📹 🙏

  • @undefined24
    @undefined24 2 місяці тому

    Awesome!

  • @svenyboyyt2304
    @svenyboyyt2304 2 місяці тому +1

    Do more videos like this please, I was currently in the process of transitioning to this pattern and I found this very helpful.

  • @michelromero7671
    @michelromero7671 2 місяці тому

    Ok, I just have 3 lines in my most recent project that are exactly like 0:07. Let's see how to fix it.

  • @jks234
    @jks234 2 місяці тому

    Sounds like this type-driven development will require detailed, ample documentation so everyone knows the invariants enforced and baked in.
    Or else it might be hell to work with.

    • @jks234
      @jks234 2 місяці тому

      Never mind. The documentation is simply a design pattern.

  • @tinrab
    @tinrab 2 місяці тому

    Lmao, nice start of the video.

  • @NuflynMagister
    @NuflynMagister 2 місяці тому +4

    Дякую, Богдане. Корисно!

  • @howhello354
    @howhello354 2 місяці тому

    Awesome

  • @kuqmua755
    @kuqmua755 27 днів тому

    There's no overflow check in deposit implementation. Its BUG

  • @Randych
    @Randych 2 місяці тому

    Tbf I've heard this 1000 times before.
    How about something really underrepresented like combinators.

  • @sunhsiang6644
    @sunhsiang6644 Місяць тому

    More use default parse etc.

  • @InMemoryOfNeo
    @InMemoryOfNeo 2 місяці тому

    this is exactly what im looking for bro. you're mother fucking AWESOME

  • @ivgadev
    @ivgadev 2 місяці тому +1

    I was already applying this concept with Domain Driven Design and value objects, however with rust it is much more powerful than in Typescript. I am in love with this language.

  • @awfultrash888
    @awfultrash888 2 місяці тому

    Oh I use arch btw

  • @shreyasjejurkar1233
    @shreyasjejurkar1233 2 місяці тому +1

    This can be achieved in other langs as well.
    For example in C# we can use records for type safe struct data and so the same what we are doing it here.

  • @sonishn5222
    @sonishn5222 2 місяці тому

    what happend to golangdojo

  • @s1v7
    @s1v7 2 місяці тому

    to be fair. using just strings in 99.99% cases is fine.

  • @Mawkler
    @Mawkler 2 місяці тому

    This is basically DDD

  • @user-sj7lk5lg3x
    @user-sj7lk5lg3x 2 місяці тому +1

    prase

  • @Thorax232
    @Thorax232 2 місяці тому +1

    "powerful software design methodology" -> basic if condition. Dude wtf?

    • @nande6471
      @nande6471 2 місяці тому

      No that wasn't the only thing, it also shows that you don't need to overuse if conditions if you can properly use the types, for example in the vid, he uses unsigned int which eliminates the to put an if condition

  • @AllanSavolainen
    @AllanSavolainen 2 місяці тому

    The bank example is really bad. And just about all the other examples can be made with untyped languages using parsing etc.

  • @ccriztoff
    @ccriztoff 2 місяці тому +1

    By the way I actually program in Rust using Vim on Arch.

  • @glydric
    @glydric 2 місяці тому

    Best video ever

  • @Wielorybkek
    @Wielorybkek 2 місяці тому +1

    Smells like Java

  • @sylvereleipertz955
    @sylvereleipertz955 2 місяці тому +3

    I don't really see the difference with OOP. This is not special to Rust type system

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

      You can do the basics of this with languages like python or C++ as well. But rust can do more. Especially when you start leveraging the trait system as well, to make the type system enforce invariants on generics, etc.

    • @silvantonio
      @silvantonio 10 днів тому

      and…. he didn't say it was exclusive to rust 😊

  • @perryc3116
    @perryc3116 Місяць тому

    "promo sm"

  • @kollpotato
    @kollpotato 2 місяці тому +2

    potato :D

  • @Anshucodes
    @Anshucodes 2 місяці тому

    ,o,

  • @no-bias-
    @no-bias- 2 місяці тому

    Why not use `TryFrom` trait?

  • @OrbitalCookie
    @OrbitalCookie 2 місяці тому

    To simplify a bit User, User can simply be replaced with type UserEditor, UserViewer, etc. The use of generics is just type masturbation here.

    • @stefano_schmidt
      @stefano_schmidt 2 місяці тому

      Type masturbation...
      What an interesting term 😆

    • @sunofabeach9424
      @sunofabeach9424 2 місяці тому

      your User and each its variant are likely to have an implementation. so you'd create a common implementation for any type T in User and then add some more traits for User, User and whatever role you'll decide to add later

    • @OrbitalCookie
      @OrbitalCookie 2 місяці тому

      @@sunofabeach9424 Wrapping shared fields in BaseUser struct that's shared between all variants is very simple

    • @OrbitalCookie
      @OrbitalCookie 2 місяці тому

      @@sunofabeach9424 Not saying it does not have its uses, I would recommend simpler composition for a start, because rarely the role carries no additional data

  • @busydying
    @busydying 2 місяці тому

    There's a convention to use `AuthError::Validation` instead of `AuthError::ValidationError`

  • @jimmylarsson5425
    @jimmylarsson5425 2 місяці тому +3

    This code could be way better like this. ua-cam.com/video/NDIU1GSBrVI/v-deo.html
    fn withdraw(&mut self, withdraw_amount: u32) -> Result {
    self.amount = self.amount.checked_sub(withdraw_amount).ok_or("not enough money!".to_string())?;
    Ok(self.amount)
    }
    the checked_sub method on u32 checks for overflow and returns None which we can convert to a result with .ok_or()
    otherwise good video :)

    • @letsgetrusty
      @letsgetrusty  2 місяці тому

      Yes could definitely be improved :)