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
  • วิทยาศาสตร์และเทคโนโลยี

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

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

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

  • @AndrewBrownK
    @AndrewBrownK หลายเดือนก่อน +156

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

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

      Isn't that the zod motto

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

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

    • @sealoftime
      @sealoftime หลายเดือนก่อน +4

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

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

  • @ya3rub101
    @ya3rub101 หลายเดือนก่อน +85

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

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

      Do you mean unrepresentable?

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

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

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

      Noboilerplate made a video about this

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

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

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

    • @letsgetrusty
      @letsgetrusty  หลายเดือนก่อน +7

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

  • @ruanluies5909
    @ruanluies5909 หลายเดือนก่อน +50

    6:32 prase -> parse

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

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

  • @Heater-v1.0.0
    @Heater-v1.0.0 หลายเดือนก่อน +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.

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

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

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

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

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

  • @Simple_OG
    @Simple_OG หลายเดือนก่อน +166

    I use arch btw

    • @maximus1172
      @maximus1172 หลายเดือนก่อน +34

      I use nix btw

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

      do you even vim?

    • @AdamFiregate
      @AdamFiregate หลายเดือนก่อน +11

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

    • @skorp5677
      @skorp5677 หลายเดือนก่อน +5

      You should rewrite Arch in Rust...

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

      ​​@@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)

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

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

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

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

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

      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

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

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

      @@TheEvertw They are different types solely due to invariants

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

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

  • @mintx1720
    @mintx1720 หลายเดือนก่อน +25

    My account has NaN money.

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

      My account is an imaginary number

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

      My account balance is of the i8 type.

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

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

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

      @@Gruak7 This is sad.

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

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

  • @artxiom
    @artxiom หลายเดือนก่อน +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!

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

    checked_sub is also useful to make that withdraw function.

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

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

  • @user-uc6wo1lc7t
    @user-uc6wo1lc7t หลายเดือนก่อน +1

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

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

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

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

      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.

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

    Very helpful! Thank you.

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

    Thanks! Best explanation as always

  • @shreyasjejurkar1233
    @shreyasjejurkar1233 หลายเดือนก่อน +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.

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

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

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

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

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

    > bank account cannot be less than zero
    damn I wish

  • @ariaden
    @ariaden หลายเดือนก่อน +39

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

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

      yeah, I think it wasn't the best example

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

      At least rust has runtime checks to prevent overflow right?

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

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

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

    Thank you, Bogdan. ✨🌞

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

    Thanks for your high quality video !

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

    Excellent

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

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

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

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

  • @eric-id6bk
    @eric-id6bk หลายเดือนก่อน

    Awesome video man ❤

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

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

  • @Felipe-53
    @Felipe-53 หลายเดือนก่อน

    Wow, great video! Thank you

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

    Great video. Thanks!

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

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

  • @dr.med.janschiefer7163
    @dr.med.janschiefer7163 หลายเดือนก่อน +2

    Very nice video.

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

    Where is that style guide from?

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

    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.

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

    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.

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

    when refinement types in rust?

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

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

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

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

      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`

  • @python-for-everyone
    @python-for-everyone หลายเดือนก่อน

    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.

  • @MateoRodriguez-ch3ek
    @MateoRodriguez-ch3ek หลายเดือนก่อน

    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.

  • @MadMathMike
    @MadMathMike 11 วันที่ผ่านมา

    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.

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

    Why you didn't use the FromStr trait?

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

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

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

    Awesome video!

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

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

  • @ivgadev
    @ivgadev หลายเดือนก่อน +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.

  • @handsanitizer2457
    @handsanitizer2457 27 วันที่ผ่านมา

    Can you make an updated rust best practices videos 📹 🙏

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

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

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

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

  • @mistamunsta
    @mistamunsta หลายเดือนก่อน +14

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

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

      Life is too short to play it safe ;)

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

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

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

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

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

      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.

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

      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.

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

    Awesome!

  • @sunhsiang6644
    @sunhsiang6644 28 วันที่ผ่านมา

    More use default parse etc.

  • @NuflynMagister
    @NuflynMagister หลายเดือนก่อน +4

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

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

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

      Resut

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

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

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

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

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

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

    meanwhile javascript devs: wait types exist?

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

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

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

    Oh I use arch btw

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

    Lmao, nice start of the video.

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

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

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

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

      Never mind. The documentation is simply a design pattern.

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

    Awesome

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

    what happend to golangdojo

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

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

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

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

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

    This is basically DDD

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

    Best video ever

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

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

  • @user-sj7lk5lg3x
    @user-sj7lk5lg3x หลายเดือนก่อน +1

    prase

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

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

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

      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

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

    Smells like Java

  • @perryc3116
    @perryc3116 19 วันที่ผ่านมา

    "promo sm"

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

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

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

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

    • @MasterHigure
      @MasterHigure หลายเดือนก่อน +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.

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

    ,o,

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

    potato :D

  • @no-bias-
    @no-bias- หลายเดือนก่อน

    Why not use `TryFrom` trait?

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

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

      Type masturbation...
      What an interesting term 😆

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

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

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

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

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

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

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

    This code could be way better like this. th-cam.com/video/NDIU1GSBrVI/w-d-xo.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  หลายเดือนก่อน

      Yes could definitely be improved :)