The magic of Rust's type system

Поділитися
Вставка
  • Опубліковано 28 лис 2024

КОМЕНТАРІ • 182

  • @letsgetrusty
    @letsgetrusty  8 місяців тому +11

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

  • @AndrewBrownK
    @AndrewBrownK 8 місяців тому +192

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

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

      Isn't that the zod motto

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

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

    • @sealoftime
      @sealoftime 8 місяців тому +7

      @@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 8 місяців тому +3

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

  • @ya3rub101
    @ya3rub101 8 місяців тому +101

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

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

      Do you mean unrepresentable?

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

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

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

      Noboilerplate made a video about this

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

      @@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 8 місяців тому +53

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

    • @letsgetrusty
      @letsgetrusty  8 місяців тому +9

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

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

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

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

      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 8 місяців тому +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 8 місяців тому

      @@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 8 місяців тому

      @@TheEvertw They are different types solely due to invariants

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

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

  • @ruanluies5909
    @ruanluies5909 8 місяців тому +52

    6:32 prase -> parse

  • @Simple_OG
    @Simple_OG 8 місяців тому +193

    I use arch btw

    • @maximus1172
      @maximus1172 8 місяців тому +39

      I use nix btw

    • @nhieljeff2156
      @nhieljeff2156 8 місяців тому +11

      do you even vim?

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

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

    • @skorp5677
      @skorp5677 8 місяців тому +6

      You should rewrite Arch in Rust...

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

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

  • @oneofpro
    @oneofpro 8 місяців тому +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.

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

      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.

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

    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 8 місяців тому +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

  • @mintx1720
    @mintx1720 8 місяців тому +36

    My account has NaN money.

    • @culturedgator
      @culturedgator 8 місяців тому +3

      My account is an imaginary number

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

      My account balance is of the i8 type.

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

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

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

      @@Gruak7 This is sad.

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

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

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

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

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

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

  • @prabesshh
    @prabesshh 8 місяців тому +3

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

  • @ariaden
    @ariaden 8 місяців тому +44

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

    • @carlosmspk
      @carlosmspk 8 місяців тому +5

      yeah, I think it wasn't the best example

    • @torinfaes6278
      @torinfaes6278 8 місяців тому +3

      At least rust has runtime checks to prevent overflow right?

    • @ianknowles
      @ianknowles 8 місяців тому +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 8 місяців тому +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 8 місяців тому +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.

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

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

  • @valhalla_dev
    @valhalla_dev 8 місяців тому +4

    > bank account cannot be less than zero
    damn I wish

  • @MaxG628
    @MaxG628 8 місяців тому +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.

    • @antifa_communist
      @antifa_communist 8 місяців тому +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.

  • @KPidS
    @KPidS 8 місяців тому +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 8 місяців тому

      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 8 місяців тому

      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`

  • @ЕвгенийКрасилов-о9о
    @ЕвгенийКрасилов-о9о 8 місяців тому +1

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

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

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

  • @chillyvanilly6352
    @chillyvanilly6352 8 місяців тому +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 8 місяців тому

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

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

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

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

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

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

    checked_sub is also useful to make that withdraw function.

    • @yuitachibana8829
      @yuitachibana8829 4 місяці тому

      Yeah I was about to comment that. But maybe he wanted to make the code more imparative so that new devs can easily understand

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

    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 8 місяців тому

    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.

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

    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.

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

    Technical stuff aside... this channel gives off a cult like feel. Listening to this presentation is like listening to a preacher.

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

    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.

  • @Locarito-ds6pl
    @Locarito-ds6pl 4 місяці тому

    7:16 I don't think the use of PhantomData is useful because UserRole are unit types with a size of zero (aka ZST), they only exists at compile time and not runtime.

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

    Where is that style guide from?

  • @elchiapp
    @elchiapp 4 місяці тому

    I don't get how this ensures that the email is valid if it's an instance of Email. We could just do let email = Email(String::from("whatever")) and pass it around. There's no way to enforce going through Email::parse.

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

    how to create number where number cannot equal 42? which type?

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

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

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

    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.

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

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

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

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

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

      Life is too short to play it safe ;)

  • @ivgadev
    @ivgadev 8 місяців тому +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.

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

    Why you didn't use the FromStr trait?

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

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

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

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

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

    when refinement types in rust?

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

    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 8 місяців тому

      Resut

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

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

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

      @@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 8 місяців тому

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

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

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

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

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

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

      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.

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

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

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

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

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

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

  • @nikitakhalov
    @nikitakhalov 3 місяці тому

    Wow, it's a really cool concept

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

    Very nice video.

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

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

  • @handsanitizer2457
    @handsanitizer2457 7 місяців тому

    Can you make an updated rust best practices videos 📹 🙏

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

    Thanks! Best explanation as always

  • @josuebarreto7687
    @josuebarreto7687 7 місяців тому

    meanwhile javascript devs: wait types exist?

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

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

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

    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.

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

      Never mind. The documentation is simply a design pattern.

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

    Excellent

  • @artxiom
    @artxiom 8 місяців тому +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!

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

    Thanks for your high quality video !

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

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

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

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

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

    Wow, great video! Thank you

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

    Great video. Thanks!

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

    Very helpful! Thank you.

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

    Thank you, Bogdan. ✨🌞

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

    Awesome video!

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

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

  • @shreyasjejurkar1233
    @shreyasjejurkar1233 8 місяців тому +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.

  • @sunhsiang6644
    @sunhsiang6644 7 місяців тому

    More use default parse etc.

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

    Awesome video man ❤

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

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

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

    i feel like you can do this even in C

  • @sonishn5222
    @sonishn5222 7 місяців тому

    what happend to golangdojo

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

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

    • @nande6471
      @nande6471 7 місяців тому

      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

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

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

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

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

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

    Oh I use arch btw

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

    Lmao, nice start of the video.

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

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

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

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

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

    Awesome!

  • @sylvereleipertz955
    @sylvereleipertz955 8 місяців тому +4

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

    • @MasterHigure
      @MasterHigure 8 місяців тому +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.

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

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

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

    This is basically DDD

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

    Awesome

  • @ПавелВоронов-н6ь
    @ПавелВоронов-н6ь 2 місяці тому

    Это просто гениально

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

    Smells like Java

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

    Best video ever

  • @Nesdac-k1l
    @Nesdac-k1l 8 місяців тому +1

    prase

  • @perryc3116
    @perryc3116 7 місяців тому

    "promo sm"

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

    potato :D

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

    Why not use `TryFrom` trait?

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

    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 8 місяців тому

      Type masturbation...
      What an interesting term 😆

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

      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 8 місяців тому

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

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

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

  • @jimmylarsson5425
    @jimmylarsson5425 8 місяців тому +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  8 місяців тому

      Yes could definitely be improved :)