ERRATA - I did not intend for the video to go black in the credits at the end, classic video editing mistake! - Not an error, but at 10:50 @edgeeffect said "It's impossible to get the Hoare's back in the stable." which deserves recognition as a decent pun. - 7:22 I say 'integer' but I'm referring to a float. I should have said 'number'. - 9:26, should be “unwrapping” with two Ps, not one
@@loganclark3642 yeah, or perhaps I should have made it clear I'm talking about the method, unwrap()ing I think that's probably where the missing p has gone! Thanks!
When I try to explain to people why I like Rust so much, I start with how humane the language is. It understands that devs don't have infinite bandwidth and can't keep all possible states and outcomes in their head and gives you tools to handle the "bad paths". Options and Results are the first examples that I use. That design philosophy is ubiquitous in the Rust community, and that makes the language a true joy to work with.
Exactly! Options and Results aren't even that clever - there's lots of other brilliant features that are revolutionary, but Options and Results do the day-to-day hard work of Rust, don't they!
Honestly, So far I like Rust merely due to how insanely convinient cargo is for... almost everything I can imagine. Hell, it even has a command to automatically format my code. It may not be perfect, but hey, I like it.
@@costelinha1867 Oh, there are a ton of reasons to use Rust. Cargo is a fantastic tool. I'm talking purely about getting people to think about how Rust can meet a need they don't know they have. Nearly all languages have linters and formaters and tools to be check the soundness of your code. I've found that if you lead with that, they will default to "I already have something like that." Are they as integrated as cargo? No, but they're already defensive. Instead, if you show them a pain you don't have to deal with, you'll pique their interest and can follow up with things like cargo.
@@tylerbloom4830 Indeed. It's just that I'm so new to Rust, and so far cargo has been the most noticable thing to me. Specially since I've had a feel... unpleasent experiences with Python's package manager. But Rust has so many cool things, as well, but with regards to safety. I never really had to worry much about that. Although in a more serious project or work enviroment it would be great to have it. Part of me is still having trouble imagining programs that can be so safe. The only real downside with Rust that I've found, is that most of the libraries I've used are somewhat new, so there aren't many tutorials for me to use, so I have to rely more on documentation. Which for many people is fine, but I have real trouble finding useful information in documentations.
I'm so relieved! I've turned down a lot of offers for boring advertisers, what I'd like to do is do relevant useful ads that further my goal of increasing Rust's adoption!
@@NoBoilerplate Your delivery seemed genuine and you were transparent about their offering, but mostly the offering itself provides a unique and interesting opportunity that could be extremely valuable to viewers. It’d also tightly related to the subject matter so it doesn’t feel like a major departure from the topic
it is just beautiful that the developers of rust think of errors as EXPECTATIONS not EXCEPTIONS this is the sort of poetic simplicity that the real programming gurus of all epochs out there are always looking for
So I appreciate I'm necro-ing a 10mo old comment, but I'm not sure where else I'd ask this question and hope for a decent result outside of this comment section: In the video, the line was used "next time you are tempted to return "false" or "null" to signify an error state, think about this" in reference to writing code with well-defined expected error states. Can "false" and "null" not be used as "expected" error states for a function, and handled logically by the calling code? This seems to me (novice programmer) like a best-practice issue rather than a language architecture issue.
@@tomtlrech1392 (1) false or null don't force the caller to deal with the possible error case like Option and Result do. (2) false or null don't convey any information about what failed or why it failed like Result does. (Option doesn't either, but that is why you'll use one over the other sometimes.)
@@NoBoilerplate Honestly i doubt a mathematician would be offended. Math uses and creates many things which (as of current knowledge) arent in the real world. But that also allows them to come up with ingenious mathematical constructs like complex numbers, quaternions, higher dimensions,... that often turn out, a few decades or centuries later, to be integral for some applied thing, be it quantum physics, fluid dynamics, or videogame graphics.
i just want to say that a few months ago i stumbled across one of your videos and it started an entire rust rabbit hole for me. now I've read the rust book and I'm making a sudoku solver in rust for practice! :D
Haha this is the exact path I took a couple of months ago. Getting excited by the videos, reading the book and writing a sudoku solver for practice. Have fun ✌️
My pleasure! I suppose one day I'll have run out of things to say about Rust (I certainly have a long list of other topics) and maybe if the sponsorship stuff takes off I'll have time to make all the videos! What a world!
The other thing to understand about rust is that the rust book encourages you to think about errors as either recoverable or unrecoverable. Recoverable errors should be Results. Unrecoverable errors should panic. What exactly the difference is is up to the library author, but things that can just fail at random (ie file reads, http requests, ect) should be recoverable. Things that break the contract (such as trying to initialize a type on an invalid value) should panic. Everything else should be recoverable. However, rust libraries(especially the standard one) frequently provide checked alternatives to functions that can panic(ie vec .get returns an option, whereas indexing returns the value but can panic if it is out of bounds). This is why the idea that rust STD panics(and that this is bad and makes rust not suited to certain kinds of tasks) kind of misses the point. Rust STD panics if you choose to use the functions that make you enforce the contract.
the crate no_panic works like this : - create an anonymous struct that impl Drop - the impl Drop call a C function that does not exists - create the struct at the function start - add std::mem::forget at the end - if the compiler prove that the code never panic => never unwind (so mem::forget is always called) - the compiler can remove this anonymous struct and the call to the C function - if the compiler fails to prove it => the linker try to find this C function and it fails
Congrats on another sponsor and thanks for the video! I recently decided to use rust by actually writing a few projects that i can complete instead of just mindlessly writing playground projects with no error handling and a bunch of unwrap()'s. Rust just never fails to amaze me with the design choices and gets more fun the more I use it.
Thank you! Not every video will likely have a sponsor, as I'm only accepting ones that are relevant. Did you see my Lightsaber video? Make sure you have a few of those best practice crates in your projects for maximum ergonomics!
Gotta say that posting a job ad on an obviously for-enthusiast channel was mindblowing. It seems so obvious now that I saw it, how have I never thought of that?
8:30 just before this slide I actually thought to myself "I don't see why you said exceptions break the flow, but claim somehow this is better", so in essence "this looks an awful lot like exceptions" I'm so amazed by the video making skills here
The trick to this is to practice the script A LOT. I read it out loud all through the editing process, and this encourages natural flow and thoughts like "hey won't people be thinking this looks like exceptions" :-D
Your macro video piqued my interest in rust, but this is the video that made me pick it up. A language that is so well-made you can genuinely create programs that you can legitimately say "under normal reasonable circumstances... it's the hardware that caused the crash, NOT the software." is AMAZING to me.
@@NoBoilerplate legitimately brilliant. As an aspiring game dev, the idea that I can make something that cannot crash by any reasonable means dramatically reduces the bug fixing stage of things. It doesn't prevent game breaking glitches but I can atleast know that the game won't hard crash on the players unless their machine is borked. Given the general resiliency of hardware this means that in general, software can be made to rarely crash at all.
@@NoBoilerplate Also as a WebAssembly supported language, I'm a tidbit of typescript, some HTML, and some CSS away from being a full stack dev for the web as a side hustle.
I am glad that this practice become popular in Android development too. Basically, you can use this pattern in many languages: create custom Result class and return it instead of raw value. You won't get rust compiler there, but using Result is pretty comfortable to use.
I just started exploring Rust 2 days back and found your videos are really helpful understanding many concepts. Thank you for all the valuable information you share. Keep up the good work. And one thing I noticed is that you just don't talk about the sponsor but share the opportunities to work with the sponsor. Never noticed anywhere else and it is amazing that you are helping people further to find opportunities. Keep rocking !!!
It's really something isn't it! It is very hard, you might need to come back to my videos to keep your motivation up! You can do it. Come talk to us on the discord server in #newbie-advice when you get stuck!
The result wrapper reminds me a lot of Haskell's Maybe type. I think working with uncertainty itself as a data type is by far the smartest way to go about it. While I really love functional programming because of how inherent safety is, Rust demonstrates that imperative languages can also truly respect the inherent danger of working with side effects in a meaningful way.
Love Haskell, I tried to make it my standard language, but the popularity problem is real. Rust snuck in a load of Monads and a HOST of other fantastic features from lisp too. Haskell + Lisp = Rust !
@NoBoilerplate i’ve bounced off of making haskell my main language a handful of times id never properly investigated rust tho - i always thought it was “a better c” and that ppl who used it a lot were similarly masochistic to c mains but i happened across some of yr videos and the way u made the big picture abt getting all the best parts of c, haskell, and lisp (mostly) via unsafe, types, macros respectively all at the same time w/o gc wew thank u so much i’m ditching js and as much as i can help it python lmao
This is a great example of why everyone should learn -haskell- functional programming! Even if you won't write code in that style, its ideas are sometimes (for me, every time) very useful in other patterns.
I have always been confused by Result, reading docs tell me what it is, and how to handle it, but I never had a feeling for why and in what situation they appear; this makes so much sense
Monads, like Option/Result (or Maybe/Either as they're usually called in FP), are one of the best ways to abstract away impurities in code. While these examples (the ones shown in the video) only account for handling the impurity of partial functions, other Monads can also abstract other impurities into a pure interface, like IO abstracting away side effects.
Found the Haskell developer! I kid, I kid. I love Haskell and I'd be using it if I thought I could get paid to write it. My journey to Rust started in Haskell (via Scala and Clojure). There's nothing magic about Result and Option, they're both implemented as enums, Rust's sum types. If you wanted to, you could make a purity system like IO in Haskell. However, I don't think people want to. What we want is for our code to work reliably, and though you CAN do that with pure functions, you can also do that almost as well with ensuring all Results are handled carefully. It's idiomatic and easy in Rust. So while I was sad that Rust didn't have purity, I actually don't find I mind, because what I care about is my programs WORKING when they compile. Rust's there, and it's practical.
@@NoBoilerplate Yup, partial functions are probably most problematic impurity. While IO impurities can occasionally cause issues, I feel like programs-as-proofs would be much more helpful for producing code that works *as intended* (rather than just works but might produce an undesired result), as we have discussed on the discord.
@@MithicSpirit The problem with programs-as-proofs is that, very often, the hard part is figuring out what the program is intended to do in the first place. That's why vast majority of programs and libraries don't start and end at v1.0. There are post-hoc modifications that don't come from the program having bugs, but from its intended purpose drifting.
This video is incredibly fascinating. As a scientist that want to use rust, this is the kind of feature that I've found frustrating in languages like C++, but rust seems to provide it in a very well thought out manner. I ask myself how this compares with Ada's protections against memory errors. These protections is the main reason why Ada is used in avionics and other critical systems. If Rust can provide equivalent safety, in a much more palatable and modern language, it is an incredible achievement.
I think it'd be extremely suitable! There are other incredible features too, here's a short playlist of mine highlighting them: th-cam.com/video/oY0XwMOSzq4/w-d-xo.html
I like that you are not teaching Rust but instead telling why Rust is unique. This is important because we have lots of programming languages doing the same things in slightly different way and syntax. It is also surprising to see that how every language is built on the same principles decided some decades ago like for instance the way they handle errors. Also no programming language has made any attempt to challenge it. Rust might not become the best language which solves every issue of the existing languages (I sincerely hope it becomes) but it still brings in a fresh perspective which will certainly change the way new programming languages or features are created.
Thank you for noticing, this is intentional. There are many excellent technical Rust tutorials, my speciality is, as you say, pitch why Rust is WORTH the effort of learning!
Yes! Huge fan, I love Haskell and functional programming's ability to just WORK (after satisfying the compiler). It's SO MUCH MORE RELAXING than reading errors in logfiles! By the way, Rust has first-class support for compiling to webassembly, and there's mature frontend frameworks such as yew.rs You may have heard that webasm is slower than js as it's still being optimised. That's true, but webasm is already faster than react - which for me means it's ready to go!
That's why I love the Rust way of coding and thinking. It has taken the good example of Lisp and Haskell. F# also has that future, but it's not totally free of the possibility of crashing since it has a mixed language pattern.
GOTCHA! But yes, I'd love that, current development is really good in the API space, but I'm not aware of ones that have Rails' scaffolding generation, that'll be great! Here's my video on my recommended rust web stack, btw: th-cam.com/video/pocWrUj68tU/w-d-xo.html
since i started to use go for more than a day, I realized errors should be handled directly. its more work in the beginning, but you gain so much control and clarity. I use python at work, but I started to really dislike exceptions. I often end up checking code from 3rd party packages just to see what could be a possible exceptions. its quite annoying. and i think rust made it even nicer to handle errors with the Result enum.
Thanks to channels like this I can confidently say Rust may be my next stop after Java, I do like Java a lot but compile-time guarantees like these are very nice
Fantastic! Do check out Kotlin on your way, that might be a much nicer way of writing Java for you? With Rust, start here fasterthanli.me/articles/a-half-hour-to-learn-rust
I had not though about rust not having null from the start, but I strongly agree with you. Having programmed in Scala, I always felt like the biggest issue there was the fact that you always had to use Java libraries, ridden with nulls. Making the Scala even feel pointless at times. Rust is it's own ecosystem, much more than Scala can ever be.
This video is very well done. So much so I had to subscribe immediately. Really great way to introduce even seasoned software developers to Rust's error handling. 👍
I was reading the Zig website and one of the things they specifically mentioned about Rust is that standard/common functions can often panic on their own even tho there are/could be situations the dev would want to proceed regardless of a particular operation, so considering this the no-panic seems even more like a godsend.
I disagree that gotos are bad in and of themselves. Their proper uses are simply rare, but there is no better solution given when its use is there. You don't need them in Rust most of the time with the ? operator, but it does affect the efficiency more than a goto would. And I disagree that Rust has no nulls. They're not directly exposed to the programmer, but an Option is practically a nullable pointer. It's just abstracted and protected. Null wasn't the problem, its interface was and I find that a very important distinction to make.
The difference between a Null and an Option is that Option clearly communicates that there is a None variant, and the type system doesn't implicitly assume that the None is not there. In languages with Null, Null can typically replace any value, so any value is implicitly an Option and almost every operation implicitly unwraps it. What happens when you hit Null unexpectedly can range anywhere from getting default results, through exceptions all the way up to undefined behavior, depending on the language. Saying that Option is just abstracted and protected Null, is like saying integers are just abstracted arrays of Booleans. To the extend it's true, it's trivial. To the extend that it's profound, it's false.
@@penguin_brian ? operator is an early return, (essentially desugars to `match fallible_value { Ok(value) => value, Err(error) => return error }`) so it's only a "goto" in the sense that "return" is a "goto"
Brilliant stuff. Having started way back with assembler and then to C, to hit the actual metal, it was a dangerous business. Fair enough, the machines had less CPU power and storage than my phone. Or my hearing aid, for that matter, so that is what you had to work with. I use rust to tinker with raspberry pi wee small machines. It is wonderful. (Obviously, I'm ancient so this is mostly hobby work)
I started off on the ZX spectrum, I remember the days! Rust makes me excited to get back and acquainted with the low level machine again, but without feeling like I'm building myself a time bomb! Check out this viewer-submitted story for a longer take that I agree with th-cam.com/video/ZFDqh3slQfU/w-d-xo.html
@@NoBoilerplate I started on a 'mini' computer, a Nova. Size of a big fridge; patching code on paper tape by hand. Physics machine. My, what a change!. I did love C when I found it; getting at the control and monitor kit for experiments. But rust lets you do that, and express it cleaner. But, as I say, those machines would have taken an age to rust-compile.
@@NoBoilerplate That story is exact.As soon as I started with Rust, I knew that this had the feel of C but better. I have written C code that is strong and robust, but it takes a lot of effort, and if you're trying to use someone else's - well, watch out. As for C++ Hmm
BAD LUCK! All the best for your recovery, I'm glad my videos are relaxing, if you run out of them there are 9 seasons of my scifi podcast you could listen to! th-cam.com/video/p3bDE9kszMc/w-d-xo.html
But there is still "unsafe" Rust code. It is even in the std library and some crates use it. Because without "unsafe" in the background, some things wouldn't be possible. The good news is, these are very spare and if something happens badly (like nulls, yes, nulls are in Rust available through "unsafe"), then you know exactly where to look. And that is extremely valuable. The "regular user" shouldn't do anything in "unsafe" Rust anyway, maybe besides in very intense moments for specific hardware like drivers.
Absolutely, see my "Turtles" video for my thoughts on the matter. If you don't have an unsafe system in your language, you force your users to continue writing C extensions. Bad!
Actually, I'd say that the regular user should need to do stuff in unsafe rust, just rarely. Oftentimes when interacting with APIs, especially the Windows API I have to use it. However, luckily it is usually just a couple functions with small bits and pieces wrapped in unsafe blocks, and that's all I need.
Wow, truly awesome work. Short, technical and to the point. And on top of that, caused some sparks of enthusiasm inside me to give it a try. P.s I am writing all this to support the channel. 😀
Your videos convinced me to start playing with Rust. You weren't my first introduction to the language but your videos hyped it up enough that I finally took the leap.
That's pretty much my goal: I'm a Rust hype man - I'm not especially expert at it (just see my previous video's ERRATA comments for proof!) but I can articulate, after some thought, what makes this language very different and exciting. I'm happy for you! Drop on to the discord if you want any help from the community!
TypeScript doesn't have an option object. It does have strictNullChecks, which requires union (rust: enum) types which contain null/undefined to be handled appropriately, but it doesn't feel like an appropriate comparison here, except as another example with null. Perhaps "optional chaining" was intended? It's a huge improvement, but still inferior to rust's Option
I appreciate Rust because despite being difficult and despite my humble programming background, it just clicks and makes sense especially the ways i can handle errors, for example i really love the `match` statement and how elegant my code looks, and so on
Ha, amazing! I did consider just recording the docs chapter by chapter for those who want to listen to it more passively, but then I crunched the numbers and it'd take WEEKS to record XD
In TS I return errors whenever I can address the error in the calling function. function squareRoot(num:number): number | Error . TS will force you to check the returned value before you can use it. If an error would mean that the function calling it could not do anything useful I like to throw it. It should go to the console as a way for me to debug so it never happens. Or in some cases map thrown exceptions to specific handlers like a specific HTTP status when working on the backend.
@@NoBoilerplate It is actually thanks to a previous video of yours about Rust match cases that I decided to implement that design where I could in TS applications.
@@NoBoilerplate I love TS. I've done a hello world with yew. Frontends are by far the most complex state systems I deal with in programming. Low level rust efficiency isn't appealing to my 100% cloud and container based team. But to never have an invalid state...........
I would have liked to see an image about railways that branches without any more rails and a comment about its meaning: **runtime panic**, the train just going off-rails in the wild world and crash, without any way to come back on rails properly, and the horrors that come with it. So, yeah not about rust but others. Great channel! 🦀
That's a fine metaphor - but Railway Oriented Programming in functional languages has a very tight well-understood meaning that honestly I'm ALREADY pushing the boundaries of! (I can feel the Haskell police after me!)
Wait! I didn't know about #[no_panic]. It's even better than that then. If this will cause an error if there is the possibility of a panic, then it means we can use it for creating incredibly small compiled code for webassembly and embedded devices. We add the #[no_panic] attribute, if there's an error, we update the code accordingly. Resulting in tiny and fast assembly code.
I think that to account for what you described at 10:14 one should instead use Rust's ground-shattering superpower called should_panic test, which blows (up) my mind to this day.
should_panic tests for panics at runtime, in a test. no_panic is a macro that ABORTS COMPILATION if the code inside the annotated function has any pathways that could panic. It won't even *compile*. I agree should_panic is great, and a good part of your testing strategy, but it's just re-writing the panic handler during your test run, nothing fancy there. no_panic hijacks the linker such that it won't link your code if there are any execution paths that might panic (even if they never would at runtime). It's *wild*!
@@NoBoilerplate I don't think that's a good idea since panic (as I see it) is a part of Rust for a reason and it's okay to panic if it's intended. Maybe I'm missing something here.
@@Randych I think you are, that's the opposite to the point I put forward in this video. With care, it's easy to write rust that never panics - that's great!
Hi and thanx for the great channel. Have you concidered to make vidows with small topics but practical functions - like open read, write files, handle files and directories, the most popular or best grates? How do you structure your programs? And a litle advice - end the vidoes with a short recap. I really like your videos and im now hooked on rust just becourse of them. Keep up the good work
Null was not an error, not anymore than inheritance that can go more than 1 level is an error. It's not easy to handle for some people but it allows for very elegant code when handled well. Rust is one of the better languages for handling null, along with SQL.
@@NoBoilerplate To be more precise: Usually you have a chain of computation, the result of the current one depends on the previous one. Example: parseToInt(someString: String): Either[Error, Int] findInMap(key: Int): Either[NotFoundError, SomeRandomType] And now you want to extract from the map given a string so you need to apply it in sequence like: parseToInt("123").map(findInMap).flatten Or: parseToInt("123").flatMap(findInMap) But this gets unwieldy for longer sequences of computation so e.g. Scala has the following syntax for it: for { parsed
@@marcing5380 I think this is literally the question mark operater pattern: let parsed = parseToInt("123")? let result = findInMap(parsed)? parsed is the right hand side of the either, as is result. doc.rust-lang.org/book/ch09-02-recoverable-errors-with-result.html
1:53 - Uh, no. Gotos are not bad because they take you out of the normal flow of execution. It's because they make reasoning about a program harder. Dijkstra covered this pretty well in his original paper. Your argument isn't even internally consistent because you present railway-oriented programming at 6:20, which is all about how to take you out of the normal flow of execution, and is completely equivalent to checked exceptions in Java. Anyway, I definitely agree with your overall point that nulls suck. New languages shouldn't have them. It was a mistake when Golang did it; we knew better by the time Golang was invented.
I should have clarified that 'normal flow of execution' is 'function calls'. That genius of Results is that they don't change function calls, you're simply returning a Result that either has the Ok() value you want, or an Err() value you must handle.
Hi Tris, sorry if I've misspelled your name, rust allows for `#![warn(panics)]` on crate level, which turns possible panics into warnings, instead of failing to build. It's more ergonomic and shows up in your lsp warnings.
It's always interesting when I look at my rust code and compare it to other languages. It usually feel pretty clean. But most of all. My rust code is just full of enums and match statements, everywhere. And Before I coded in rust, I barely ever used Enums.
Hey there, I love your videos, they are like a small meditation for me! Let me ask you a question, the font you are using, is it a real font or are thous merged >=, #[, and == signes added later on? and could you share the font or thous sings with me? it love to see them in my code
Oh have I got a treat for you. The merged giphs are called "ligatures" and there's LOADS of programming fonts that have them built in! The font I am using here is Fira Code Nerd Font. It's great! But there's loads of options available. Join my discord server and asking people in #programming what fonts they use and you'll get an avalanche of recommendations :-)
@@NoBoilerplate Fire Code is what I'm using too... but It seams like I never invested enough time in it! Thank's a lot, I'll dive deeper into ligatures and join your discord! Edit: I checked whats wrong with my setup and it seams that my terminal does not like ligatures! will try kittening my self later this day, I think that will help. (shame on me for using the gnome terminal)
This seems quite similar to the way Go does it, where it returns two values, the result and a possible error, and before using the result you check if the error is nil; if it isnt you handle it in some way, like propagating it up. Although having both be the same return value, just a different type, does lend itself to prevent a lazy programmer from skipping the error check and writing code that can crash.
Yes, it's a shame Go does it this way. It's MUCH BETTER than using exceptions, however! But Rust's way (ie, Haskell's way etc) is the correct way. A sum type (called Result) where the left-hand side is the type you expect, and the right-hand side is the error value.
Exceptions don't suck, they're simply a poor fit for 1) ensuring your functions are total and 2) a language that can't simply jump directly to an earlier stack frame because it needs to drop values in the intermediate frames. In a garbage-collected language they're an efficient way of aborting a thread of execution (for lack of a better term); think about algorithms or problem solvers that can hit a dead end and then need to go down another path of the search space. They're also handy for higher-order functions, in cases where you're expecting a function argument that doesn't fail. Sometimes you can prove an exception can't be thrown (e.g. your code's invariants ensure some array index is always in bounds) and thus the function being passed in is effectively exception-free in that circumstance. In the same situation you'd unwrap the Error blindly.
"They're also handy for higher-order functions, in cases where you're expecting a function argument that doesn't fail" Are you saying that exceptions are useful in this case because your functions can assume that their arguments cant fail - because the exception system will handle that for them? Allowing your type system to be ignorant of errors is not a good feature!
@@NoBoilerplate There's no way to implement exceptions that wouldn't "escape" the type system. If the exception is part of the function's type like Java's checked exceptions, they're completely equivalent to a Result enum because every function between the thrower and the catcher is also forced to have the exception in its type. Like I said, the equivalent situation in Rust would involve unwrapping the Result blindly to create another function which always returns a value. There's nothing else to do because we're starting from the premise that the rest of your code was supposed to rule out an error situation by design. If you're wrong, you get a panic, which Rusts's type system is also ignorant of, and is effectively an exception that's you're encouraged not to catch. What else can you do in a branch of code that's not supposed to exist and that undoubtedly indicates a bug? This is already how Rust handles integer overflow; you're supposed to ensure it won't happen and the compiler inserts panics in debug builds to help you catch that if it does. I still maintain it's an important tool in GC languages because you'd be forced to pay a performance penalty in certain situations if you didn't have it. And when the same thing happens in Rust, Rust doesn't take the tool away, it tells you to wield it with caution. In short, I think exceptions are best used "locally" in situations where you're writing both the thrower and the potential catcher. In a public API where you don't control the caller, and when the possible error is the result of something beyond the caller's control (e.g. I/O), the error should be part of the type so it can't be ignored.
@@ArmandoDoval In many ways, exceptions and the Result system are solving the same thing, but for me the Result type (which is not part of the language - it's just another Enum, albeit one that is used everywhere in the standard library) feels more integrated into the language. It's not an extra system added on top of the simple functional language.
@@NoBoilerplate I was mainly referring to the return value. 0 (or something like buffer length) on success, and negative on error. Depending on the implementation it either directly return the error (negative int) or you have to look up errno. My main reason for comparison is because you usually check for it with if statements, and stay in the same flow/context, this also allows you to retry or whatever. By directly returning you can more closely mimic the exception behavior. The syntax isn't that nice though, and forgetting to properly check the return value can easily cause weird bugs (often segfaults) your program
@@jetseverschuren Yeah, I nearly put something in the video about the similarity! However I don't have enough experience with C/C++ to really know that I'd be doing the right thing!
In the real world, there are also cosmic rays which can randomly flip bits in your program's memory. So I'm sure it's still possible to get a pointer which points to somewhere invalid, if the program runs for a sufficiently long period, which would eventually give you a crash. Which is just to say, I don't think it's possible to have completely crash-free code, as long as it's running on physical hardware. If it's running on virtual hardware, then perhaps all the memory and even the CPU registers can have redundancy for detecting errors. The closest thing we have to this for physical machines is ECC but it's only for one place where things could be stored.
Absolutely, shit happens! This kind of attitude is why Rust's error system (the Result type) doesn't try to cover all cases. There remain the few cases of hardware failure I talk about in this video where the only sensible thing is to panic!() and crash the program.
It is practical. And it has a huge package repository with pretty much everything you might need. Everybody can use it right now. The problem of Haskell is that if you have an IT background then just learning is not enough, you have many things to unlearn from procedural way of thinking. Yes, it's not easy, but... there is no "unsafe" :)
@@qandak I love Haskell, the main reason I say it's not practical is popularity, nothing against the language itself. It's not practical for me to build a haskell team, I can't find juniors, I can afford seniors, and there's like 100 in all of London, and they're already making bank in quant XD
@@qandak You say 'no unsafe' like it's a good thing. unsafe in rust is like IO, in haskell, - IO functions aren't BAD functions, you have to have side effects somewhere right? unsafe blocks are the same. Direct pointer access is useful (Haskell does it in FFI inside IO functions downloads.haskell.org/ghc/9.0.1/docs/html/users_guide/exts/ffi.html?highlight=capiffi#extension-CApiFFI) but instead of isolating functional purity, Rust isolates pointer operations. They're BOTH really good ideas!
@@NoBoilerplate I was trying to "defend" Haskell a little bit in front of people who are not familiar with it, but I know that you are :) As for the rest, I fully agree with you, and have nothing useful to add!
So Rust's error handling system is effectively the same as Java's checked exceptions, minus the actual exceptions. Java's checked exceptions always received a bad rap in the developer community; I never understood why. I always found checked exceptions very useful in alerting me at compile time what could go wrong. I'm glad that Rust in its own way is also enforcing error handling like Java did.
I'm afraid this is not the way I see it, do re-watch my video again. I know the `?` operator makes them feel similar to checked exceptions, but they are not the same. The problem with exceptions is that they take you outside of the normal function/method call flow of programming and on to a new flow: try/catch. This complexity is not worth having in a language. If your language doesn't have sum types (ie something to build the Result type) you have no option but to build it in. If your language has sum types, you don't need to build an exception system.
@@NoBoilerplate My comment was not so much about exceptions vs `Result` types, and more about the "checked" in checked exceptions in Java. I honestly feel that exceptions are still a better approach than the `Result` type for a couple of reasons, but that is beyond the scope of my comment. What I do have a strong opinion on, is that the compiler should force the developer to deal with potential recoverable errors. Java does this by ensuring that the developer either catches or throws the checked exception up the stack. Rust does this by ensuring that the developer either deconstructs the `Result` type or returns it back up the stack. Contrast that to what C# does, for example: a method can throw an exception the program could easily recover from, but you will not find out unless you go out of your way to dig through the method's documentation, or later at runtime at 3AM on a Saturday night.
Goto's are bad -> exceptions are bad is a bad analogy. There is nothing wrong with exceptions and panic/unwrap is exception handling. There are basically two ways to handle errors predictably: 1. Wrap the result in a box (Optional) 2. Stop and unwind until reaching the code meant to handle errors. Both options are equivalent. The box option gives you operators like "if the box contains what I want, then apply a function to it and put the result back in the box", chaining the whole success path. With some generalization, you will end up with reactive programming. Both options bring execution to some top-most outer code which will finally open the box and handle the error if there is any or catch the exception. Error handling in both cases is usually the same - delegate option to repeat the action to the consumer. But! Boxes do not require language support (most languages include them). However, without syntactic sugar working with boxes is a real pain. Exceptions are always supported by clean syntax (well, Rust did it meh with unwrap, but it is Rust being Rust...), which is why people tend to gravitate towards them.
Panic is indeed like a bad exception system, but that's not what we use in Rust, we use the Result system that is baked-in to the standard library and all libraries. Panicking is possible to effectively remove from your Rust code, and even, as I showed in the video, prove that crucial functions do not panic. Everything's equivalent, that argument doesn't matter. The differences between exceptions and Results-passing are what I'm interested in, and what this video shows. Syntactic sugar? Rust supports Result returning using a single character, `?`. That's pretty sweet!
I'm not the right person to do that kind of thing. HOWEVER you should read everything Amos has ever written fasterthanli.me/articles/when-rustc-explodes
No higher kinded types (despite having higher kinded lifetimes), constantly inventing new syntax for different monads, and the bane of my existence: no type level sets
Honestly, returning errors exclusively as values isn't really a rare thing that Rust stands out in doing. In Golang, returning an error alongside the result is built into the language itself, requiring the programmer to handle any error immediately. In other languages, you can return errors, sometimes as part of a option/union type (error | result) or a tuple (e.g. in C#). It's more that Rust and Go don't offer exceptions, preventing that sort of flow jumping - but even exceptions are handy in certain scenarios, such as for web app that wants to catch any kind of error and fail a request gracefully. (Granted, such errors might be exceedingly rare in Rust.)
Go came so close to doing things right. Consider this pattern, which we see everywhere in go: result , _ = func() How often have you seen the error ignored like this? Go returns errors as tuples, which is not good enough for me. It's an approximation of sum types, Rust's Enum type, but it's not good enough. Go does not have Sum types, and so had to use something worse, alas.
It's true that go has nulls (nils), but they are present only and only if you are dealing with raw pointers. If you use anything that isn't a pointer, go uses the concept of "zero value". This is useful because if you see that you have a pointer, you already know that you might be dealing with a nil pointer. This feature is SADLY not present in Java. If you have for example a String, that might be null no problem! PS: In Go you can also panic and then use recover, which basically mimicks the behaviour of exceptions. I strongly advise against coding with panics in mind!!!! Always return the error!!!!!!
My understanding of Go's 'zero value' is that it's a default type-correct value, for example: The zero value of type Time is January 1, year 1, 00:00:00.000000000 UTC. Though this is better than a null value, it's still extremely surprising when you thought you had a real time! Thank you for the correction!
@@WolfrostWasTaken I think it's not as good as you're making it out to be twitter.com/fasterthanlime/status/1455483635876966401?t=9-k6QyA1mu28FeijVy4yJw&s=19
I just did a small Rust project demoing six different methods the language provides for safe error handling and propagation using Result: "is_ok()", "match", "let...else", "if let ... else", "unwrap_or()", and "?" are there even more ? (If anybody is interested, I will upload the code somewhere...) -Michael
I can easily see this blowing in my face if my computation has several levels of nested Result return (and lickely VBox), Does Rust have syntactic sugar for dealing with multiple lifts (e.g. the do block in Haskell)? A few code I've seen present patterns similar to JS's callback hell.
I know what you mean, also like the (->) function in clojure - the first thing that comes to mind is the result.map() function, which you can chain to mutate the value on the left hand side of a Result safely, only unwrapping it at the end doc.rust-lang.org/std/result/enum.Result.html#method.map Unrelated, but important: the question "does have rust have syntactic sugar for X" is always "yes" because of macros! :-)
Im just starting to look into rust. I was under the impression that it was simply a low-level C or C++ replacement. Coming to find that i was very wrong. It seems to have borrowed some of the best features from some of the strict functional languages (algebraic data types, exhaustive pattern matching, idomatic use of the maybe monad, immutability) but all of this is wrapped up in much more pragmatic package. For those more sewsonrd in rust, am i on the right track with my interpretation?
You are absolutely right, I'd been a python web developer for 15 years, and I'm not excited by low-level features (though I'm delighted the language has them!) but the high-level features that will make my life SO much better! Here's a short playlists of highlights for videos of mine I recommend, for your interest: th-cam.com/video/oY0XwMOSzq4/w-d-xo.html
fun fact: rust code can crash, if there is unsafe in stblib or crates, it can, probably, segfault randomly, but its much better to have a high level language that can handle low-level with unsafe than C++
After reading pages of dart.dev/null-safety/understanding-null-safety this I still don't quite understand it. The simplest null safety mechanism is to just not have them in your language.
@@NoBoilerplate as far as i understood it, dart started out as smth similar to typescript, being able to compile into JavaScript in early versions every variable and type could also return null, so for eg. a str to int parser could return null and you'd have no way of knowing it today this is not possible. if you want your function to return either the type or null, you'll have to append a question mark to the end of the type name am example would be: int? parseStr(String num) { ... } dart now makes you deal with null and there are a number of nice syntax that help. for eg. if you want a default instead a null occurs you can do the following int parsed = parseStr("hi") ?? 10 if parseStr returns null, parsed will hold the value of 10
7:02 has to be a better way to Err, such as: include the x and y in the error, but also maybe the stack trace of where the error occurred, because I mean, imagine this error bubbles up to main, how do you see the stacktrace? and the x and y values?
I'm interested in what people would think of exceptions if you had to define exactly what exceptions could be thrown from a specific function, because then it would give almost as much information as returning a Result and using ? in rust.
That's actually how java works, as far as I know: public function throws X Y Z It's not the explicit function signature that is the genius of the Result type in Rust. It's that the signature affects how you can CALL the function. It's perfectly possible to write (say) python code that happily ignores the fact that exceptions may occur. When you get them, you wrap the dodgy call in a try: except block. In Rust: You can't even PRETEND that the error doesn't exist, Rust forces you to handle the error cases up front. More work, certainly. But this is key to the ability to FINISH writing Rust projects. Once it compiles, it works. I talked about this here: th-cam.com/video/Z3xPIYHKSoI/w-d-xo.html
@@NoBoilerplate Thanks for the detailed response! As far as I could see from a quick search, Java sometimes requires you to declare that a function `throws` an exception but not if it's a RuntimeException (WHY?!?). Or something like that. I guess you could make a syntax where functions throwing an exception must be called differently but then exceptions become basically the same as rust Results but with extra steps. I wonder how this would apply to algebraic effects, since they have similar behaviour to exceptions and have often been quoted as "monads 2.0", but seem to be lacking some of the good features you've talked about.
If you are just using the language and want to handle errors, you can just use the question mark operator, a single byte! Eg, parsed_number = num.parse()?; doc.rust-lang.org/book/ch09-02-recoverable-errors-with-result.html
reminds me of something a university lecture told my class: "You can be as early as you like and be on time, but if you're 1 second late, you're late."
I'm not an java expert but people have told me that Java has various types for handling nulls, eg: docs.oracle.com/javase/8/docs/api/java/util/Optional.html
Within the scope of a language that uses exceptions, I do rather like how Java handles this. It does still have all of the problems that exceptions provide, but you must (usually) either handle all possible exceptions or explicitly state that a function throws an exception. As opposed to something like JavaScript where at any given moment, you could get any possible error anywhere in the call stack. Java has its problems, but for something as bloated and verbose as it is, sometimes it does have elegant solutions to its own inherent design problems.
Years since I programmed Java, so I might be out-of-date, but the two criticisms of how it does it are (a) Runtime Exceptions do not have to be declared, and (b) programmers complained about it becoming excessive verbose. Possibly (b) is due to poor technique in passing all exceptions as is to higher level code - not sure if Java has better techniques to deal with this (e.g. wrapping the exception).
That syntax isn't boilerplate: That syntax gives us superpowers: In your example, I see three unwraps and an expect. This tells me that your line of code has exactly 4 ways it can panic at runtime. Solutions to this are to handle the errors correctly, perhaps using match, or to bubble up the error to the calling function with ? In my code review, I will recommend replacing those unwraps with appropriate safe alternatives. We may, after discussion, decide that the risks are very small, and to leave some of them in, perhaps converting them to .expect("reason") with our reasoning captured. This same line in javascript looks like: doSomething(data[0].name.first()) Four places it can crash, zero indication it will do so. Further investigation is needed. Enormous context is needed to be safe in other popular languages, zero is needed in Rust. --- Your Arc tells me that it's a thread-safe type that can be passed around safely, but might be slow due to locking. Many other languages use this exact feature in their core types, and you must read the documentation to find this out. My channel is called no boilerplate, which is unnecessary code that doesn't add value. Rust has lots of code that give more value than any other popular language. I'm still learning! Do check out my other rust videos, perhaps starting with this one th-cam.com/video/Z3xPIYHKSoI/w-d-xo.html I'd love to know what you think!
Are you using the same colour as the background of TH-cam in dark mode for the background of your video to achieve as seamless video? That's pretty clever
Started by accident, but now I'm keeping it! It's a shade off, if you look closely, your comment has inspired me to make it perfect for the next video!
> TypeScript attempts to paper over the damage with its own options type [...] This is not true. TS doesn't have an option type, TS has unions (not disjoint/tagged unions, but set unions). E.g. if your function returns a string or null, its return type is `string | null`, which is roughly equivalent to `Option` (without lifetimes, obviously). So TS can perfectly track nullability (both `null` and `undefined`) and has done so in my TS projects for years. I also want to point out that from a type perspective, unions are actually more powerful than tagged unions. You can create tagged unions in TS (e.g. you can make your own `Option` type), but you can't do TS's unions in Rust (type safe without `Any` that is). This isn't to say that TS's type system is better. It's not sound for one (not due to unions, though).
I think we might be both a victim of ignorance of the other's language! I've looked into TS's tagged unions, and they seem like Rust's enums. They're both sum types, no? In fact, there is nothing special about Rust's Option type, it's just an enum doc.rust-lang.org/std/option/enum.Option.html (same for Results, they're both enums) You can do incredible things when you have sum types!
@@NoBoilerplate Sum types are tagged/disjoint unions. To see the difference consider: These are tagged unions: enum LeftRight { Left(L), Right(R), } Assume A, B and C are different types. Then: LeftRight< LeftRight, C> is different from LeftRight< A, LeftRight > Also LeftRight is different from i32 For set union types, their "sum" is associative AorB = A | B; BorC = B | C; AorB | AorC is the same as A | B | C (there is no indication whether B came from the AorB or from BorC) Also A | A is the same as A. If your language has set unions, you can create tagged unions, by wrapping the variants in unique generic wrapper structs: struct Left(L); struct Right(R); LeftRight = Left | Right; Because Left and Right are different types, their set union lets you differentiate whether the inner T comes from left or right. Rust does not have set union types. They wouldn't work well. To see why, consider, what if you take set union of multiple types that are generic over multiple lifetimes each? Nightmare fuel.
Another way to crash is with a stack overflow. In recursive code, you might not be able to compute at compile time the amount of stack space required. Do we agree that Rust can't prevent that type of crash?
I have great news! Though you're quite right, there's no tail call optimisation in Rust yet, it is planned with the `become` keyword. But even better than that, just as with anything, you can have this feature today using the macro system: lib.rs/crates/tailcall
ERRATA
- I did not intend for the video to go black in the credits at the end, classic video editing mistake!
- Not an error, but at 10:50 @edgeeffect said "It's impossible to get the Hoare's back in the stable." which deserves recognition as a decent pun.
- 7:22 I say 'integer' but I'm referring to a float. I should have said 'number'.
- 9:26, should be “unwrapping” with two Ps, not one
At 9:25, it’s should be “unwrapping” with two p’s, not one
@@loganclark3642 yeah, or perhaps I should have made it clear I'm talking about the method,
unwrap()ing
I think that's probably where the missing p has gone! Thanks!
When I try to explain to people why I like Rust so much, I start with how humane the language is. It understands that devs don't have infinite bandwidth and can't keep all possible states and outcomes in their head and gives you tools to handle the "bad paths". Options and Results are the first examples that I use. That design philosophy is ubiquitous in the Rust community, and that makes the language a true joy to work with.
Exactly! Options and Results aren't even that clever - there's lots of other brilliant features that are revolutionary, but Options and Results do the day-to-day hard work of Rust, don't they!
Honestly, So far I like Rust merely due to how insanely convinient cargo is for... almost everything I can imagine.
Hell, it even has a command to automatically format my code. It may not be perfect, but hey, I like it.
@@costelinha1867 Oh, there are a ton of reasons to use Rust. Cargo is a fantastic tool. I'm talking purely about getting people to think about how Rust can meet a need they don't know they have.
Nearly all languages have linters and formaters and tools to be check the soundness of your code. I've found that if you lead with that, they will default to "I already have something like that." Are they as integrated as cargo? No, but they're already defensive. Instead, if you show them a pain you don't have to deal with, you'll pique their interest and can follow up with things like cargo.
@@costelinha1867 It's pretty perfect! The dependency handling is gorgeous - read up on the package "yank"ing behaviour!
@@tylerbloom4830 Indeed. It's just that I'm so new to Rust, and so far cargo has been the most noticable thing to me. Specially since I've had a feel... unpleasent experiences with Python's package manager.
But Rust has so many cool things, as well, but with regards to safety. I never really had to worry much about that. Although in a more serious project or work enviroment it would be great to have it. Part of me is still having trouble imagining programs that can be so safe.
The only real downside with Rust that I've found, is that most of the libraries I've used are somewhat new, so there aren't many tutorials for me to use, so I have to rely more on documentation. Which for many people is fine, but I have real trouble finding useful information in documentations.
"Just as in life, worthwhile interaction comes with the possibility of failure", goddamn, you're taking me on a philosophical spiral
Thank you :-)
That's a way of doing a sponsor ive never seen before and i am 100% here for it. Awesome way to send the call to new hires
I'm so relieved! I've turned down a lot of offers for boring advertisers, what I'd like to do is do relevant useful ads that further my goal of increasing Rust's adoption!
@@NoBoilerplate The ad read made me subscribe 🤩
@@minnow1337 wow! What did you like specifically so I can keep finding good sponsors?
@@NoBoilerplate Your delivery seemed genuine and you were transparent about their offering, but mostly the offering itself provides a unique and interesting opportunity that could be extremely valuable to viewers. It’d also tightly related to the subject matter so it doesn’t feel like a major departure from the topic
@@minnow1337 Thank you so much! I'm hoping to find other nice Rust companies to point my viewers too - LET ME KNOW IF YOU KNOW ANY!
it is just beautiful that the developers of rust think of errors as EXPECTATIONS not EXCEPTIONS
this is the sort of poetic simplicity that the real programming gurus of all epochs out there are always looking for
Such a simple shift!
So I appreciate I'm necro-ing a 10mo old comment, but I'm not sure where else I'd ask this question and hope for a decent result outside of this comment section:
In the video, the line was used "next time you are tempted to return "false" or "null" to signify an error state, think about this" in reference to writing code with well-defined expected error states. Can "false" and "null" not be used as "expected" error states for a function, and handled logically by the calling code? This seems to me (novice programmer) like a best-practice issue rather than a language architecture issue.
@@tomtlrech1392 (1) false or null don't force the caller to deal with the possible error case like Option and Result do. (2) false or null don't convey any information about what failed or why it failed like Result does. (Option doesn't either, but that is why you'll use one over the other sometimes.)
@@tomtlrech1392i think the point is that if it's enforced by the compiler, then people are more likely to do the right thing
"Of course, unlike mathematicians, we live in the real world"
- No Boilerplate
A little mean, but you get the idea!
my math teacher after seeing this video:
@@NoBoilerplate Honestly i doubt a mathematician would be offended. Math uses and creates many things which (as of current knowledge) arent in the real world. But that also allows them to come up with ingenious mathematical constructs like complex numbers, quaternions, higher dimensions,... that often turn out, a few decades or centuries later, to be integral for some applied thing, be it quantum physics, fluid dynamics, or videogame graphics.
@@RepChrisIt depends on wtf we are meaning by the "real world".
Johnny with his 300 bananas feeling real targeted right now 😂
i just want to say that a few months ago i stumbled across one of your videos and it started an entire rust rabbit hole for me. now I've read the rust book and I'm making a sudoku solver in rust for practice! :D
Fantastic! This is music to my ears, I started this Rust series to get the word out about this life changing language!
Haha this is the exact path I took a couple of months ago. Getting excited by the videos, reading the book and writing a sudoku solver for practice. Have fun ✌️
me too, i just made a tetris clone based in the terminal.
I didn't know until I started watching NB that I wanted "fast, technical videos", but man am I hooked. Thank you!
My pleasure! I suppose one day I'll have run out of things to say about Rust (I certainly have a long list of other topics) and maybe if the sponsorship stuff takes off I'll have time to make all the videos! What a world!
The other thing to understand about rust is that the rust book encourages you to think about errors as either recoverable or unrecoverable. Recoverable errors should be Results. Unrecoverable errors should panic. What exactly the difference is is up to the library author, but things that can just fail at random (ie file reads, http requests, ect) should be recoverable. Things that break the contract (such as trying to initialize a type on an invalid value) should panic. Everything else should be recoverable. However, rust libraries(especially the standard one) frequently provide checked alternatives to functions that can panic(ie vec .get returns an option, whereas indexing returns the value but can panic if it is out of bounds). This is why the idea that rust STD panics(and that this is bad and makes rust not suited to certain kinds of tasks) kind of misses the point. Rust STD panics if you choose to use the functions that make you enforce the contract.
Clear explanation, thank you!
the crate no_panic works like this :
- create an anonymous struct that impl Drop
- the impl Drop call a C function that does not exists
- create the struct at the function start
- add std::mem::forget at the end
- if the compiler prove that the code never panic => never unwind (so mem::forget is always called)
- the compiler can remove this anonymous struct and the call to the C function
- if the compiler fails to prove it => the linker try to find this C function and it fails
Damn that's a massive hack but also super smart
Wow yeah that's wild! I'm delighted its possible XD
Note however panics are not necessarily unsafe or even undefined behavior. Hence the ability to catch panics in code that implements UnwindSafe
Congrats on another sponsor and thanks for the video! I recently decided to use rust by actually writing a few projects that i can complete instead of just mindlessly writing playground projects with no error handling and a bunch of unwrap()'s. Rust just never fails to amaze me with the design choices and gets more fun the more I use it.
Thank you! Not every video will likely have a sponsor, as I'm only accepting ones that are relevant.
Did you see my Lightsaber video? Make sure you have a few of those best practice crates in your projects for maximum ergonomics!
@@NoBoilerplate I did! I'll make sure to put those tools to good use at some point :)
@@creeperkafasi Amazing! GO FORTH AND CHANGE THE WORLD! (be nice)
Gotta say that posting a job ad on an obviously for-enthusiast channel was mindblowing. It seems so obvious now that I saw it, how have I never thought of that?
Now all I have to do is get lightning to strike twice!
8:30 just before this slide I actually thought to myself "I don't see why you said exceptions break the flow, but claim somehow this is better", so in essence "this looks an awful lot like exceptions"
I'm so amazed by the video making skills here
The trick to this is to practice the script A LOT. I read it out loud all through the editing process, and this encourages natural flow and thoughts like "hey won't people be thinking this looks like exceptions" :-D
same thought lol
Your macro video piqued my interest in rust, but this is the video that made me pick it up. A language that is so well-made you can genuinely create programs that you can legitimately say "under normal reasonable circumstances... it's the hardware that caused the crash, NOT the software." is AMAZING to me.
It's *wonderful* isn't it? Good wording, hardware can cause crashes, not software. NICE
@@NoBoilerplate legitimately brilliant. As an aspiring game dev, the idea that I can make something that cannot crash by any reasonable means dramatically reduces the bug fixing stage of things. It doesn't prevent game breaking glitches but I can atleast know that the game won't hard crash on the players unless their machine is borked. Given the general resiliency of hardware this means that in general, software can be made to rarely crash at all.
@@jabadahut50 RIGHT
@@NoBoilerplate Also as a WebAssembly supported language, I'm a tidbit of typescript, some HTML, and some CSS away from being a full stack dev for the web as a side hustle.
Every video, you manage to put words on why this language is so beautiful. Great job as always!
Thank you so much, that's the goal!
I am glad that this practice become popular in Android development too. Basically, you can use this pattern in many languages: create custom Result class and return it instead of raw value. You won't get rust compiler there, but using Result is pretty comfortable to use.
Google announced first-class Android support at last week's GOSL event! rewatch here: opensourcelive.withgoogle.com/events/rust-day-2022
Wow the first ad I've ever seen on TH-cam that's actually relevant and meaningful to me lol. Thanks ditto!
WHAT big shoes to fill, I hope that I can find more sponsors like Ditto, I've turned down lots of unrelated sponsors as I don't want to annoy you!
I just started exploring Rust 2 days back and found your videos are really helpful understanding many concepts. Thank you for all the valuable information you share. Keep up the good work.
And one thing I noticed is that you just don't talk about the sponsor but share the opportunities to work with the sponsor. Never noticed anywhere else and it is amazing that you are helping people further to find opportunities.
Keep rocking !!!
Randomly stumbled on your vids and now I'm forcing myself to learn rust. Quite hard but fun. Keep up this phenomenon Quality vids
It's really something isn't it! It is very hard, you might need to come back to my videos to keep your motivation up! You can do it. Come talk to us on the discord server in #newbie-advice when you get stuck!
The result wrapper reminds me a lot of Haskell's Maybe type. I think working with uncertainty itself as a data type is by far the smartest way to go about it. While I really love functional programming because of how inherent safety is, Rust demonstrates that imperative languages can also truly respect the inherent danger of working with side effects in a meaningful way.
Love Haskell, I tried to make it my standard language, but the popularity problem is real.
Rust snuck in a load of Monads and a HOST of other fantastic features from lisp too.
Haskell + Lisp = Rust !
@NoBoilerplate i’ve bounced off of making haskell my main language a handful of times
id never properly investigated rust tho - i always thought it was “a better c” and that ppl who used it a lot were similarly masochistic to c mains
but i happened across some of yr videos and the way u made the big picture abt getting all the best parts of c, haskell, and lisp (mostly) via unsafe, types, macros respectively
all at the same time
w/o gc
wew
thank u so much i’m ditching js and as much as i can help it python lmao
This is a great example of why everyone should learn -haskell- functional programming! Even if you won't write code in that style, its ideas are sometimes (for me, every time) very useful in other patterns.
I have always been confused by Result, reading docs tell me what it is, and how to handle it, but I never had a feeling for why and in what situation they appear; this makes so much sense
Monads, like Option/Result (or Maybe/Either as they're usually called in FP), are one of the best ways to abstract away impurities in code. While these examples (the ones shown in the video) only account for handling the impurity of partial functions, other Monads can also abstract other impurities into a pure interface, like IO abstracting away side effects.
Found the Haskell developer! I kid, I kid. I love Haskell and I'd be using it if I thought I could get paid to write it. My journey to Rust started in Haskell (via Scala and Clojure).
There's nothing magic about Result and Option, they're both implemented as enums, Rust's sum types. If you wanted to, you could make a purity system like IO in Haskell. However, I don't think people want to.
What we want is for our code to work reliably, and though you CAN do that with pure functions, you can also do that almost as well with ensuring all Results are handled carefully. It's idiomatic and easy in Rust.
So while I was sad that Rust didn't have purity, I actually don't find I mind, because what I care about is my programs WORKING when they compile. Rust's there, and it's practical.
@@NoBoilerplate Yup, partial functions are probably most problematic impurity. While IO impurities can occasionally cause issues, I feel like programs-as-proofs would be much more helpful for producing code that works *as intended* (rather than just works but might produce an undesired result), as we have discussed on the discord.
@@MithicSpirit The problem with programs-as-proofs is that, very often, the hard part is figuring out what the program is intended to do in the first place. That's why vast majority of programs and libraries don't start and end at v1.0. There are post-hoc modifications that don't come from the program having bugs, but from its intended purpose drifting.
This video is incredibly fascinating. As a scientist that want to use rust, this is the kind of feature that I've found frustrating in languages like C++, but rust seems to provide it in a very well thought out manner.
I ask myself how this compares with Ada's protections against memory errors. These protections is the main reason why Ada is used in avionics and other critical systems. If Rust can provide equivalent safety, in a much more palatable and modern language, it is an incredible achievement.
I think it'd be extremely suitable!
There are other incredible features too, here's a short playlist of mine highlighting them: th-cam.com/video/oY0XwMOSzq4/w-d-xo.html
I like that you are not teaching Rust but instead telling why Rust is unique. This is important because we have lots of programming languages doing the same things in slightly different way and syntax. It is also surprising to see that how every language is built on the same principles decided some decades ago like for instance the way they handle errors. Also no programming language has made any attempt to challenge it. Rust might not become the best language which solves every issue of the existing languages (I sincerely hope it becomes) but it still brings in a fresh perspective which will certainly change the way new programming languages or features are created.
Thank you for noticing, this is intentional. There are many excellent technical Rust tutorials, my speciality is, as you say, pitch why Rust is WORTH the effort of learning!
I was first introduced to the concept of a Return type in Elm and it blew me away. Elm is such a marvelous language.
Yes! Huge fan, I love Haskell and functional programming's ability to just WORK (after satisfying the compiler). It's SO MUCH MORE RELAXING than reading errors in logfiles!
By the way, Rust has first-class support for compiling to webassembly, and there's mature frontend frameworks such as yew.rs
You may have heard that webasm is slower than js as it's still being optimised. That's true, but webasm is already faster than react - which for me means it's ready to go!
@@NoBoilerplate holy crap, you may have just majorly changed my life. I had no idea about Yew.
That's why I love the Rust way of coding and thinking. It has taken the good example of Lisp and Haskell. F# also has that future, but it's not totally free of the possibility of crashing since it has a mixed language pattern.
I'm so excited we have a POPULAR language with all these great features in!
From the title I was expecting framework like Ruby on Rails, but you know, with Rust :D
GOTCHA! But yes, I'd love that, current development is really good in the API space, but I'm not aware of ones that have Rails' scaffolding generation, that'll be great!
Here's my video on my recommended rust web stack, btw: th-cam.com/video/pocWrUj68tU/w-d-xo.html
since i started to use go for more than a day, I realized errors should be handled directly. its more work in the beginning, but you gain so much control and clarity. I use python at work, but I started to really dislike exceptions. I often end up checking code from 3rd party packages just to see what could be a possible exceptions. its quite annoying. and i think rust made it even nicer to handle errors with the Result enum.
I've heard it's a terrible time fasterthanli.me/articles/i-want-off-mr-golangs-wild-ride
Thanks to channels like this I can confidently say Rust may be my next stop after Java, I do like Java a lot but compile-time guarantees like these are very nice
Fantastic! Do check out Kotlin on your way, that might be a much nicer way of writing Java for you?
With Rust, start here fasterthanli.me/articles/a-half-hour-to-learn-rust
I had not though about rust not having null from the start, but I strongly agree with you.
Having programmed in Scala, I always felt like the biggest issue there was the fact that you always had to use Java libraries, ridden with nulls. Making the Scala even feel pointless at times.
Rust is it's own ecosystem, much more than Scala can ever be.
Exactly, and the same problem happens with Typescript, javascript leaks through.
Rust is a revolution, and I'm HERE for it!
This video is very well done. So much so I had to subscribe immediately. Really great way to introduce even seasoned software developers to Rust's error handling. 👍
Thank you so much! Check out my other Rust videos here th-cam.com/video/Q3AhzHq8ogs/w-d-xo.html
I was reading the Zig website and one of the things they specifically mentioned about Rust is that standard/common functions can often panic on their own even tho there are/could be situations the dev would want to proceed regardless of a particular operation, so considering this the no-panic seems even more like a godsend.
I'd love a link to that, Zig is a very well-thought-out language.
I disagree that gotos are bad in and of themselves. Their proper uses are simply rare, but there is no better solution given when its use is there. You don't need them in Rust most of the time with the ? operator, but it does affect the efficiency more than a goto would.
And I disagree that Rust has no nulls. They're not directly exposed to the programmer, but an Option is practically a nullable pointer. It's just abstracted and protected.
Null wasn't the problem, its interface was and I find that a very important distinction to make.
you see how the unknown state of the box is inside a safe Option, right? No bare nulls in the safe subset of rust is a HUGE deal, don't minimise it.
The difference between a Null and an Option is that Option clearly communicates that there is a None variant, and the type system doesn't implicitly assume that the None is not there. In languages with Null, Null can typically replace any value, so any value is implicitly an Option and almost every operation implicitly unwraps it. What happens when you hit Null unexpectedly can range anywhere from getting default results, through exceptions all the way up to undefined behavior, depending on the language.
Saying that Option is just abstracted and protected Null, is like saying integers are just abstracted arrays of Booleans. To the extend it's true, it's trivial. To the extend that it's profound, it's false.
Playing devils advocate here: Isn't the ? operator just like a goto?
@@penguin_brian ? operator is an early return, (essentially desugars to `match fallible_value { Ok(value) => value, Err(error) => return error }`) so it's only a "goto" in the sense that "return" is a "goto"
Brilliant stuff. Having started way back with assembler and then to C, to hit the actual metal, it was a dangerous business. Fair enough, the machines had less CPU power and storage than my phone. Or my hearing aid, for that matter, so that is what you had to work with. I use rust to tinker with raspberry pi wee small machines. It is wonderful. (Obviously, I'm ancient so this is mostly hobby work)
I started off on the ZX spectrum, I remember the days! Rust makes me excited to get back and acquainted with the low level machine again, but without feeling like I'm building myself a time bomb!
Check out this viewer-submitted story for a longer take that I agree with th-cam.com/video/ZFDqh3slQfU/w-d-xo.html
@@NoBoilerplate I started on a 'mini' computer, a Nova. Size of a big fridge; patching code on paper tape by hand. Physics machine. My, what a change!. I did love C when I found it; getting at the control and monitor kit for experiments.
But rust lets you do that, and express it cleaner.
But, as I say, those machines would have taken an age to rust-compile.
@@NoBoilerplate That story is exact.As soon as I started with Rust, I knew that this had the feel of C but better. I have written C code that is strong and robust, but it takes a lot of effort, and if you're trying to use someone else's - well, watch out. As for C++ Hmm
congrats on the sponsorship!! i have covid and your videos are very relaxing :)
BAD LUCK! All the best for your recovery, I'm glad my videos are relaxing, if you run out of them there are 9 seasons of my scifi podcast you could listen to! th-cam.com/video/p3bDE9kszMc/w-d-xo.html
@@NoBoilerplate bet :) ty
Splendid video! Amazing as always! If anyone tries to contradict this video, they're a madman!
But there is still "unsafe" Rust code. It is even in the std library and some crates use it. Because without "unsafe" in the background, some things wouldn't be possible.
The good news is, these are very spare and if something happens badly (like nulls, yes, nulls are in Rust available through "unsafe"), then you know exactly where to look. And that is extremely valuable. The "regular user" shouldn't do anything in "unsafe" Rust anyway, maybe besides in very intense moments for specific hardware like drivers.
Absolutely, see my "Turtles" video for my thoughts on the matter.
If you don't have an unsafe system in your language, you force your users to continue writing C extensions. Bad!
Actually, I'd say that the regular user should need to do stuff in unsafe rust, just rarely. Oftentimes when interacting with APIs, especially the Windows API I have to use it. However, luckily it is usually just a couple functions with small bits and pieces wrapped in unsafe blocks, and that's all I need.
I just woke up, why am i so interested in this all of a sudden...
Wow, truly awesome work. Short, technical and to the point. And on top of that, caused some sparks of enthusiasm inside me to give it a try.
P.s I am writing all this to support the channel. 😀
You are so kind! Rust is REALLY exciting to me, and that's why I just HAD to start this series!
Your videos convinced me to start playing with Rust. You weren't my first introduction to the language but your videos hyped it up enough that I finally took the leap.
Also your lost terminal podcast is dope as hell. I really enjoy that.
That's pretty much my goal: I'm a Rust hype man - I'm not especially expert at it (just see my previous video's ERRATA comments for proof!) but I can articulate, after some thought, what makes this language very different and exciting.
I'm happy for you! Drop on to the discord if you want any help from the community!
I'm so pleased, thank you so much for saying so.
I like this Rust series, but I LOVE Lost Terminal, it feels like my life's work so far!
TypeScript doesn't have an option object. It does have strictNullChecks, which requires union (rust: enum) types which contain null/undefined to be handled appropriately, but it doesn't feel like an appropriate comparison here, except as another example with null. Perhaps "optional chaining" was intended? It's a huge improvement, but still inferior to rust's Option
Ah apologies, I was thinking of Python's type hints, which are called Options, too. Thank you for the correction!
I appreciate Rust because despite being difficult and despite my humble programming background, it just clicks and makes sense especially the ways i can handle errors, for example i really love the `match` statement and how elegant my code looks, and so on
Absolutely
Hey, just wanted to say that I now read the rust docs in your voice :D
Ha, amazing! I did consider just recording the docs chapter by chapter for those who want to listen to it more passively, but then I crunched the numbers and it'd take WEEKS to record XD
These are the only videos which I have to slow down (0.75) instead of speedup (1.5) as usual. :)
I try to respect your time. I figure it's much easier to pause a video than keep skipping forward!
@@NoBoilerplate Totally agree! And I really appreciate your style.
@@plato4ek Thank you so much!
In TS I return errors whenever I can address the error in the calling function. function squareRoot(num:number): number | Error . TS will force you to check the returned value before you can use it. If an error would mean that the function calling it could not do anything useful I like to throw it. It should go to the console as a way for me to debug so it never happens. Or in some cases map thrown exceptions to specific handlers like a specific HTTP status when working on the backend.
how do you know the calling function is able to address the error? Aren't you making assumptions about code you don't know?
Aha, good to know, thank you for telling me!
@@NoBoilerplate It is actually thanks to a previous video of yours about Rust match cases that I decided to implement that design where I could in TS applications.
@@adamd0ggg2 fantastic! TS is great if you have to work in a javascript environment. But I dream of yew.rs
@@NoBoilerplate I love TS. I've done a hello world with yew. Frontends are by far the most complex state systems I deal with in programming. Low level rust efficiency isn't appealing to my 100% cloud and container based team. But to never have an invalid state...........
the intro analogy was so good
Thank you! I didn't create the "exceptions shouldn't be exceptional" argument - functional languages have been saying this for decades!
I would have liked to see an image about railways that branches without any more rails and a comment about its meaning: **runtime panic**, the train just going off-rails in the wild world and crash, without any way to come back on rails properly, and the horrors that come with it. So, yeah not about rust but others.
Great channel! 🦀
That's a fine metaphor - but Railway Oriented Programming in functional languages has a very tight well-understood meaning that honestly I'm ALREADY pushing the boundaries of! (I can feel the Haskell police after me!)
Wait! I didn't know about #[no_panic]. It's even better than that then. If this will cause an error if there is the possibility of a panic, then it means we can use it for creating incredibly small compiled code for webassembly and embedded devices. We add the #[no_panic] attribute, if there's an error, we update the code accordingly. Resulting in tiny and fast assembly code.
Why would it make it small? I think there's other optimisations you can make (no std style) for this
"Lets be real, shit happens" - No Boilerplate
I think that to account for what you described at 10:14 one should instead use Rust's ground-shattering superpower called should_panic test, which blows (up) my mind to this day.
should_panic tests for panics at runtime, in a test.
no_panic is a macro that ABORTS COMPILATION if the code inside the annotated function has any pathways that could panic.
It won't even *compile*.
I agree should_panic is great, and a good part of your testing strategy, but it's just re-writing the panic handler during your test run, nothing fancy there.
no_panic hijacks the linker such that it won't link your code if there are any execution paths that might panic (even if they never would at runtime).
It's *wild*!
@@NoBoilerplate I don't think that's a good idea since panic (as I see it) is a part of Rust for a reason and it's okay to panic if it's intended.
Maybe I'm missing something here.
@@Randych I think you are, that's the opposite to the point I put forward in this video. With care, it's easy to write rust that never panics - that's great!
Hi and thanx for the great channel. Have you concidered to make vidows with small topics but practical functions - like open read, write files, handle files and directories, the most popular or best grates? How do you structure your programs? And a litle advice - end the vidoes with a short recap. I really like your videos and im now hooked on rust just becourse of them. Keep up the good work
Great suggestions, thank you!
Null was not an error, not anymore than inheritance that can go more than 1 level is an error.
It's not easy to handle for some people but it allows for very elegant code when handled well.
Rust is one of the better languages for handling null, along with SQL.
I didn't make it up www.infoq.com/presentations/Null-References-The-Billion-Dollar-Mistake-Tony-Hoare/
@@NoBoilerplate I'm not saying he didn't say it. But it wasn't a mistake, just a concept that's too powerful for many minds.
This looks like a MONAD implementation but it's very nice it's there by default
Yes indeed, the Either monad, well spotted!
I tell people that Rust is Haskell and Lisp snuck into the cool kids party in C's clothing :-D
@@NoBoilerplate But is there some idiomatic way to compose it, like "for {} yield" in Scala or "do" in Haskell?
@@marcing5380 I don't quite understand what you are asking for, could you say more?
@@NoBoilerplate To be more precise:
Usually you have a chain of computation, the result of the current one depends on the previous one. Example:
parseToInt(someString: String): Either[Error, Int]
findInMap(key: Int): Either[NotFoundError, SomeRandomType]
And now you want to extract from the map given a string so you need to apply it in sequence like:
parseToInt("123").map(findInMap).flatten
Or:
parseToInt("123").flatMap(findInMap)
But this gets unwieldy for longer sequences of computation so e.g. Scala has the following syntax for it:
for {
parsed
@@marcing5380 I think this is literally the question mark operater pattern:
let parsed = parseToInt("123")?
let result = findInMap(parsed)?
parsed is the right hand side of the either, as is result.
doc.rust-lang.org/book/ch09-02-recoverable-errors-with-result.html
1:53 - Uh, no. Gotos are not bad because they take you out of the normal flow of execution. It's because they make reasoning about a program harder. Dijkstra covered this pretty well in his original paper. Your argument isn't even internally consistent because you present railway-oriented programming at 6:20, which is all about how to take you out of the normal flow of execution, and is completely equivalent to checked exceptions in Java.
Anyway, I definitely agree with your overall point that nulls suck. New languages shouldn't have them. It was a mistake when Golang did it; we knew better by the time Golang was invented.
I should have clarified that 'normal flow of execution' is 'function calls'.
That genius of Results is that they don't change function calls, you're simply returning a Result that either has the Ok() value you want, or an Err() value you must handle.
Hi Tris, sorry if I've misspelled your name,
rust allows for `#![warn(panics)]` on crate level, which turns possible panics into warnings, instead of failing to build. It's more ergonomic and shows up in your lsp warnings.
I would LOVE this to be true, but I can't find it. Do you have a link? Thank you!
The way no_panic apparently works is hilarious :)
I don't QUITE know how it works, but it does!
It's always interesting when I look at my rust code and compare it to other languages. It usually feel pretty clean. But most of all. My rust code is just full of enums and match statements, everywhere. And Before I coded in rust, I barely ever used Enums.
Same!
I had hoped this video had some references to Ruby on Rails.
Though it's not got rails' maturity, I really love rocket.rs !
@@NoBoilerplate Interesting!
Hey there, I love your videos, they are like a small meditation for me! Let me ask you a question, the font you are using, is it a real font or are thous merged >=, #[, and == signes added later on? and could you share the font or thous sings with me? it love to see them in my code
Oh have I got a treat for you. The merged giphs are called "ligatures" and there's LOADS of programming fonts that have them built in!
The font I am using here is Fira Code Nerd Font.
It's great! But there's loads of options available. Join my discord server and asking people in #programming what fonts they use and you'll get an avalanche of recommendations :-)
@@NoBoilerplate Fire Code is what I'm using too... but It seams like I never invested enough time in it! Thank's a lot, I'll dive deeper into ligatures and join your discord!
Edit: I checked whats wrong with my setup and it seams that my terminal does not like ligatures! will try kittening my self later this day, I think that will help.
(shame on me for using the gnome terminal)
1:49 Because it’s an “exception!” /j
This seems quite similar to the way Go does it, where it returns two values, the result and a possible error, and before using the result you check if the error is nil; if it isnt you handle it in some way, like propagating it up. Although having both be the same return value, just a different type, does lend itself to prevent a lazy programmer from skipping the error check and writing code that can crash.
Yes, it's a shame Go does it this way. It's MUCH BETTER than using exceptions, however! But Rust's way (ie, Haskell's way etc) is the correct way.
A sum type (called Result) where the left-hand side is the type you expect, and the right-hand side is the error value.
Exceptions don't suck, they're simply a poor fit for 1) ensuring your functions are total and 2) a language that can't simply jump directly to an earlier stack frame because it needs to drop values in the intermediate frames.
In a garbage-collected language they're an efficient way of aborting a thread of execution (for lack of a better term); think about algorithms or problem solvers that can hit a dead end and then need to go down another path of the search space.
They're also handy for higher-order functions, in cases where you're expecting a function argument that doesn't fail. Sometimes you can prove an exception can't be thrown (e.g. your code's invariants ensure some array index is always in bounds) and thus the function being passed in is effectively exception-free in that circumstance. In the same situation you'd unwrap the Error blindly.
"They're also handy for higher-order functions, in cases where you're expecting a function argument that doesn't fail"
Are you saying that exceptions are useful in this case because your functions can assume that their arguments cant fail - because the exception system will handle that for them?
Allowing your type system to be ignorant of errors is not a good feature!
@@NoBoilerplate There's no way to implement exceptions that wouldn't "escape" the type system. If the exception is part of the function's type like Java's checked exceptions, they're completely equivalent to a Result enum because every function between the thrower and the catcher is also forced to have the exception in its type.
Like I said, the equivalent situation in Rust would involve unwrapping the Result blindly to create another function which always returns a value. There's nothing else to do because we're starting from the premise that the rest of your code was supposed to rule out an error situation by design. If you're wrong, you get a panic, which Rusts's type system is also ignorant of, and is effectively an exception that's you're encouraged not to catch. What else can you do in a branch of code that's not supposed to exist and that undoubtedly indicates a bug? This is already how Rust handles integer overflow; you're supposed to ensure it won't happen and the compiler inserts panics in debug builds to help you catch that if it does.
I still maintain it's an important tool in GC languages because you'd be forced to pay a performance penalty in certain situations if you didn't have it. And when the same thing happens in Rust, Rust doesn't take the tool away, it tells you to wield it with caution. In short, I think exceptions are best used "locally" in situations where you're writing both the thrower and the potential catcher. In a public API where you don't control the caller, and when the possible error is the result of something beyond the caller's control (e.g. I/O), the error should be part of the type so it can't be ignored.
@@ArmandoDoval In many ways, exceptions and the Result system are solving the same thing, but for me the Result type (which is not part of the language - it's just another Enum, albeit one that is used everywhere in the standard library) feels more integrated into the language. It's not an extra system added on top of the simple functional language.
This feels a bit like C's way of handling errors, but the compiler actually checks if you properly handle errors
errno stuff?
@@NoBoilerplate I was mainly referring to the return value. 0 (or something like buffer length) on success, and negative on error. Depending on the implementation it either directly return the error (negative int) or you have to look up errno. My main reason for comparison is because you usually check for it with if statements, and stay in the same flow/context, this also allows you to retry or whatever. By directly returning you can more closely mimic the exception behavior. The syntax isn't that nice though, and forgetting to properly check the return value can easily cause weird bugs (often segfaults) your program
@@jetseverschuren Yeah, I nearly put something in the video about the similarity! However I don't have enough experience with C/C++ to really know that I'd be doing the right thing!
In the real world, there are also cosmic rays which can randomly flip bits in your program's memory. So I'm sure it's still possible to get a pointer which points to somewhere invalid, if the program runs for a sufficiently long period, which would eventually give you a crash. Which is just to say, I don't think it's possible to have completely crash-free code, as long as it's running on physical hardware.
If it's running on virtual hardware, then perhaps all the memory and even the CPU registers can have redundancy for detecting errors. The closest thing we have to this for physical machines is ECC but it's only for one place where things could be stored.
Absolutely, shit happens! This kind of attitude is why Rust's error system (the Result type) doesn't try to cover all cases. There remain the few cases of hardware failure I talk about in this video where the only sensible thing is to panic!() and crash the program.
Looks like I need to start learning Rust 😊
"The only way we could avoid errors is by not interacting with the real world"
I'm looking at you, Haskell. Yeah, yeah, IO-Action whatever.
heh, Haskell has a lot to teach us, but it has a lot to learn before we can use it practically.
It is practical. And it has a huge package repository with pretty much everything you might need. Everybody can use it right now.
The problem of Haskell is that if you have an IT background then just learning is not enough, you have many things to unlearn from procedural way of thinking.
Yes, it's not easy, but... there is no "unsafe" :)
@@qandak I love Haskell, the main reason I say it's not practical is popularity, nothing against the language itself. It's not practical for me to build a haskell team, I can't find juniors, I can afford seniors, and there's like 100 in all of London, and they're already making bank in quant XD
@@qandak You say 'no unsafe' like it's a good thing. unsafe in rust is like IO, in haskell, - IO functions aren't BAD functions, you have to have side effects somewhere right? unsafe blocks are the same. Direct pointer access is useful (Haskell does it in FFI inside IO functions downloads.haskell.org/ghc/9.0.1/docs/html/users_guide/exts/ffi.html?highlight=capiffi#extension-CApiFFI) but instead of isolating functional purity, Rust isolates pointer operations. They're BOTH really good ideas!
@@NoBoilerplate I was trying to "defend" Haskell a little bit in front of people who are not familiar with it, but I know that you are :)
As for the rest, I fully agree with you, and have nothing useful to add!
So Rust's error handling system is effectively the same as Java's checked exceptions, minus the actual exceptions. Java's checked exceptions always received a bad rap in the developer community; I never understood why. I always found checked exceptions very useful in alerting me at compile time what could go wrong. I'm glad that Rust in its own way is also enforcing error handling like Java did.
I'm afraid this is not the way I see it, do re-watch my video again.
I know the `?` operator makes them feel similar to checked exceptions, but they are not the same.
The problem with exceptions is that they take you outside of the normal function/method call flow of programming and on to a new flow: try/catch.
This complexity is not worth having in a language.
If your language doesn't have sum types (ie something to build the Result type) you have no option but to build it in.
If your language has sum types, you don't need to build an exception system.
@@NoBoilerplate My comment was not so much about exceptions vs `Result` types, and more about the "checked" in checked exceptions in Java. I honestly feel that exceptions are still a better approach than the `Result` type for a couple of reasons, but that is beyond the scope of my comment.
What I do have a strong opinion on, is that the compiler should force the developer to deal with potential recoverable errors. Java does this by ensuring that the developer either catches or throws the checked exception up the stack. Rust does this by ensuring that the developer either deconstructs the `Result` type or returns it back up the stack. Contrast that to what C# does, for example: a method can throw an exception the program could easily recover from, but you will not find out unless you go out of your way to dig through the method's documentation, or later at runtime at 3AM on a Saturday night.
Goto's are bad -> exceptions are bad is a bad analogy. There is nothing wrong with exceptions and panic/unwrap is exception handling. There are basically two ways to handle errors predictably:
1. Wrap the result in a box (Optional)
2. Stop and unwind until reaching the code meant to handle errors.
Both options are equivalent. The box option gives you operators like "if the box contains what I want, then apply a function to it and put the result back in the box", chaining the whole success path. With some generalization, you will end up with reactive programming. Both options bring execution to some top-most outer code which will finally open the box and handle the error if there is any or catch the exception. Error handling in both cases is usually the same - delegate option to repeat the action to the consumer.
But! Boxes do not require language support (most languages include them). However, without syntactic sugar working with boxes is a real pain. Exceptions are always supported by clean syntax (well, Rust did it meh with unwrap, but it is Rust being Rust...), which is why people tend to gravitate towards them.
Panic is indeed like a bad exception system, but that's not what we use in Rust, we use the Result system that is baked-in to the standard library and all libraries.
Panicking is possible to effectively remove from your Rust code, and even, as I showed in the video, prove that crucial functions do not panic.
Everything's equivalent, that argument doesn't matter. The differences between exceptions and Results-passing are what I'm interested in, and what this video shows.
Syntactic sugar? Rust supports Result returning using a single character, `?`. That's pretty sweet!
Rust on rails would be amazing!
rocket.rs is getting there!
All I see and hear are good parts of rust.
I'd love to see some drawbacks as well, because no language is perfect.
I'm not the right person to do that kind of thing. HOWEVER you should read everything Amos has ever written fasterthanli.me/articles/when-rustc-explodes
No higher kinded types (despite having higher kinded lifetimes), constantly inventing new syntax for different monads, and the bane of my existence: no type level sets
Honestly, returning errors exclusively as values isn't really a rare thing that Rust stands out in doing. In Golang, returning an error alongside the result is built into the language itself, requiring the programmer to handle any error immediately. In other languages, you can return errors, sometimes as part of a option/union type (error | result) or a tuple (e.g. in C#). It's more that Rust and Go don't offer exceptions, preventing that sort of flow jumping - but even exceptions are handy in certain scenarios, such as for web app that wants to catch any kind of error and fail a request gracefully. (Granted, such errors might be exceedingly rare in Rust.)
Go came so close to doing things right. Consider this pattern, which we see everywhere in go:
result , _ = func()
How often have you seen the error ignored like this? Go returns errors as tuples, which is not good enough for me.
It's an approximation of sum types, Rust's Enum type, but it's not good enough. Go does not have Sum types, and so had to use something worse, alas.
It's true that go has nulls (nils), but they are present only and only if you are dealing with raw pointers. If you use anything that isn't a pointer, go uses the concept of "zero value". This is useful because if you see that you have a pointer, you already know that you might be dealing with a nil pointer.
This feature is SADLY not present in Java. If you have for example a String, that might be null no problem!
PS: In Go you can also panic and then use recover, which basically mimicks the behaviour of exceptions. I strongly advise against coding with panics in mind!!!! Always return the error!!!!!!
My understanding of Go's 'zero value' is that it's a default type-correct value, for example:
The zero value of type Time is January 1, year 1, 00:00:00.000000000 UTC.
Though this is better than a null value, it's still extremely surprising when you thought you had a real time!
Thank you for the correction!
@@NoBoilerplate Yeah it might be weird but at least the program doesn't crash ^^
@@WolfrostWasTaken I think it's not as good as you're making it out to be twitter.com/fasterthanlime/status/1455483635876966401?t=9-k6QyA1mu28FeijVy4yJw&s=19
wait to learn more!
I just did a small Rust project demoing six different methods the language provides for safe error handling and propagation using Result:
"is_ok()", "match", "let...else", "if let ... else", "unwrap_or()", and "?"
are there even more ?
(If anybody is interested, I will upload the code somewhere...)
-Michael
I can easily see this blowing in my face if my computation has several levels of nested Result return (and lickely VBox), Does Rust have syntactic sugar for dealing with multiple lifts (e.g. the do block in Haskell)? A few code I've seen present patterns similar to JS's callback hell.
I know what you mean, also like the (->) function in clojure - the first thing that comes to mind is the result.map() function, which you can chain to mutate the value on the left hand side of a Result safely, only unwrapping it at the end doc.rust-lang.org/std/result/enum.Result.html#method.map
Unrelated, but important: the question "does have rust have syntactic sugar for X" is always "yes" because of macros! :-)
Im just starting to look into rust. I was under the impression that it was simply a low-level C or C++ replacement. Coming to find that i was very wrong. It seems to have borrowed some of the best features from some of the strict functional languages (algebraic data types, exhaustive pattern matching, idomatic use of the maybe monad, immutability) but all of this is wrapped up in much more pragmatic package. For those more sewsonrd in rust, am i on the right track with my interpretation?
You are absolutely right, I'd been a python web developer for 15 years, and I'm not excited by low-level features (though I'm delighted the language has them!) but the high-level features that will make my life SO much better!
Here's a short playlists of highlights for videos of mine I recommend, for your interest: th-cam.com/video/oY0XwMOSzq4/w-d-xo.html
fun fact: rust code can crash, if there is unsafe in stblib or crates, it can, probably, segfault randomly, but its much better to have a high level language that can handle low-level with unsafe than C++
Yeah! It is possible to build a reliable system out of unreliable parts.
Nowhere is this more true than Rust!
what do you think about how dart handles nulls? with their sound-null-safety?
After reading pages of dart.dev/null-safety/understanding-null-safety this I still don't quite understand it.
The simplest null safety mechanism is to just not have them in your language.
@@NoBoilerplate as far as i understood it, dart started out as smth similar to typescript, being able to compile into JavaScript
in early versions every variable and type could also return null, so for eg. a str to int parser could return null and you'd have no way of knowing it
today this is not possible. if you want your function to return either the type or null, you'll have to append a question mark to the end of the type name
am example would be:
int? parseStr(String num) { ... }
dart now makes you deal with null and there are a number of nice syntax that help. for eg. if you want a default instead a null occurs you can do the following
int parsed = parseStr("hi") ?? 10
if parseStr returns null, parsed will hold the value of 10
7:02 has to be a better way to Err, such as: include the x and y in the error, but also maybe the stack trace of where the error occurred, because I mean, imagine this error bubbles up to main, how do you see the stacktrace? and the x and y values?
Oh yes indeed, this is very possible. My favourite way of presenting this is to use crates.io/crates/color-eyre
I know this is not what the video is about, but a Rust framework that could replace Ruby on Rails would be cool. :)
Rocket is getting there! rocket.rs
I'm interested in what people would think of exceptions if you had to define exactly what exceptions could be thrown from a specific function, because then it would give almost as much information as returning a Result and using ? in rust.
That's actually how java works, as far as I know:
public function throws X Y Z
It's not the explicit function signature that is the genius of the Result type in Rust. It's that the signature affects how you can CALL the function.
It's perfectly possible to write (say) python code that happily ignores the fact that exceptions may occur. When you get them, you wrap the dodgy call in a try: except block.
In Rust: You can't even PRETEND that the error doesn't exist, Rust forces you to handle the error cases up front.
More work, certainly. But this is key to the ability to FINISH writing Rust projects. Once it compiles, it works.
I talked about this here: th-cam.com/video/Z3xPIYHKSoI/w-d-xo.html
@@NoBoilerplate Thanks for the detailed response! As far as I could see from a quick search, Java sometimes requires you to declare that a function `throws` an exception but not if it's a RuntimeException (WHY?!?). Or something like that. I guess you could make a syntax where functions throwing an exception must be called differently but then exceptions become basically the same as rust Results but with extra steps.
I wonder how this would apply to algebraic effects, since they have similar behaviour to exceptions and have often been quoted as "monads 2.0", but seem to be lacking some of the good features you've talked about.
do we need to manually throw specific exception? cant rely on default error exception?
If you are just using the language and want to handle errors, you can just use the question mark operator, a single byte! Eg,
parsed_number = num.parse()?;
doc.rust-lang.org/book/ch09-02-recoverable-errors-with-result.html
At 2:35 you said that unlike mathematicians, we live in the real world. As a mathematician, I can assure you that we too live in the real world. :)
Did xkcd lie to me?! xkcd.com/435/
Make invalid state unrepresentable!
THIS IS THE WAY
In Soviet Rust you do not invalidate. Soviet Rust invalidates you.
In Soviet Rust, you don't correct your Rust code. Your Rust code corrects you.
Great video!
Thank you!
Which font are you using in the slides that contain rust code?
All slides are in Fira Code Nerd Font, with ligatures :-)
There is only one way in which something can go right, and an infinite ways in which it can go wrong.
reminds me of something a university lecture told my class: "You can be as early as you like and be on time, but if you're 1 second late, you're late."
What did you mean by "Java now has options" when referring to handling nulls?
I'm not an java expert but people have told me that Java has various types for handling nulls, eg: docs.oracle.com/javase/8/docs/api/java/util/Optional.html
Within the scope of a language that uses exceptions, I do rather like how Java handles this. It does still have all of the problems that exceptions provide, but you must (usually) either handle all possible exceptions or explicitly state that a function throws an exception. As opposed to something like JavaScript where at any given moment, you could get any possible error anywhere in the call stack. Java has its problems, but for something as bloated and verbose as it is, sometimes it does have elegant solutions to its own inherent design problems.
Unchecked exceptions exist, unfortunately.
Years since I programmed Java, so I might be out-of-date, but the two criticisms of how it does it are (a) Runtime Exceptions do not have to be declared, and (b) programmers complained about it becoming excessive verbose. Possibly (b) is due to poor technique in passing all exceptions as is to higher level code - not sure if Java has better techniques to deal with this (e.g. wrapping the exception).
@@FADHsquared like I said, usually. But hey that’s Java for you
Sure, Java's better than javascript here, but that's no surprise.
Exceptions THEMSELVES are the problem.
@@NoBoilerplate Oh, believe me I couldn’t agree more. I just find it interesting how much both better and worse Java can be at some of these things.
at least for me, rust encourages unwrap().unwrap().expect("lol").unwrap() and Arc which are both awful.
That syntax isn't boilerplate: That syntax gives us superpowers:
In your example, I see three unwraps and an expect.
This tells me that your line of code has exactly 4 ways it can panic at runtime. Solutions to this are to handle the errors correctly, perhaps using match, or to bubble up the error to the calling function with ?
In my code review, I will recommend replacing those unwraps with appropriate safe alternatives.
We may, after discussion, decide that the risks are very small, and to leave some of them in, perhaps converting them to .expect("reason") with our reasoning captured.
This same line in javascript looks like:
doSomething(data[0].name.first())
Four places it can crash, zero indication it will do so. Further investigation is needed. Enormous context is needed to be safe in other popular languages, zero is needed in Rust.
---
Your Arc tells me that it's a thread-safe type that can be passed around safely, but might be slow due to locking.
Many other languages use this exact feature in their core types, and you must read the documentation to find this out.
My channel is called no boilerplate, which is unnecessary code that doesn't add value. Rust has lots of code that give more value than any other popular language. I'm still learning!
Do check out my other rust videos, perhaps starting with this one th-cam.com/video/Z3xPIYHKSoI/w-d-xo.html
I'd love to know what you think!
Are you using the same colour as the background of TH-cam in dark mode for the background of your video to achieve as seamless video? That's pretty clever
Started by accident, but now I'm keeping it! It's a shade off, if you look closely, your comment has inspired me to make it perfect for the next video!
> TypeScript attempts to paper over the damage with its own options type [...]
This is not true. TS doesn't have an option type, TS has unions (not disjoint/tagged unions, but set unions). E.g. if your function returns a string or null, its return type is `string | null`, which is roughly equivalent to `Option` (without lifetimes, obviously). So TS can perfectly track nullability (both `null` and `undefined`) and has done so in my TS projects for years.
I also want to point out that from a type perspective, unions are actually more powerful than tagged unions. You can create tagged unions in TS (e.g. you can make your own `Option` type), but you can't do TS's unions in Rust (type safe without `Any` that is). This isn't to say that TS's type system is better. It's not sound for one (not due to unions, though).
I think we might be both a victim of ignorance of the other's language!
I've looked into TS's tagged unions, and they seem like Rust's enums. They're both sum types, no?
In fact, there is nothing special about Rust's Option type, it's just an enum doc.rust-lang.org/std/option/enum.Option.html
(same for Results, they're both enums)
You can do incredible things when you have sum types!
@@NoBoilerplate Sum types are tagged/disjoint unions. To see the difference consider:
These are tagged unions:
enum LeftRight {
Left(L),
Right(R),
}
Assume A, B and C are different types. Then:
LeftRight< LeftRight, C> is different from LeftRight< A, LeftRight >
Also LeftRight is different from i32
For set union types, their "sum" is associative
AorB = A | B;
BorC = B | C;
AorB | AorC is the same as A | B | C
(there is no indication whether B came from the AorB or from BorC)
Also A | A is the same as A.
If your language has set unions, you can create tagged unions, by wrapping the variants in unique generic wrapper structs:
struct Left(L);
struct Right(R);
LeftRight = Left | Right;
Because Left and Right are different types, their set union lets you differentiate whether the inner T comes from left or right.
Rust does not have set union types. They wouldn't work well. To see why, consider, what if you take set union of multiple types that are generic over multiple lifetimes each? Nightmare fuel.
great video, as always
Thank you so much!
Hey, Python doesn't have null!
...
It has None.
AttributeError: 'NoneType' object has no attribute 'ohno'
I have tried dating a variable in Python, but then I quickly learned that it simply wasn't my type.
@@NoBoilerplate This video is on rails, it should be
NoMethodError: undefined method `ohno' for nil:NilClass
@@Kiaulen oh that just gave me deep PTSD XD
Another way to crash is with a stack overflow. In recursive code, you might not be able to compute at compile time the amount of stack space required. Do we agree that Rust can't prevent that type of crash?
I have great news! Though you're quite right, there's no tail call optimisation in Rust yet, it is planned with the `become` keyword.
But even better than that, just as with anything, you can have this feature today using the macro system: lib.rs/crates/tailcall
Tor is being remade in Rust! The project is called Arti. What are your thoughts?
Saw it a few weeks back on HN - I tried it and it worked out of the box, just a cargo install away! Fantastic!
Idk if it's your excellent narration or Rust amazing error handling that's causing me to tear up
Watch a few episodes of my podcast to be sure th-cam.com/video/p3bDE9kszMc/w-d-xo.html
(for science)
Working with null had teached me to be a pessimist developer, assuming everything will be wrong at first.
Thank you for your service. You may now retire into the green pastures of Rust. The water's fine!
what do you make your slideshows with?
Obsidian.md
Check out my "Lightsaber" video for a demo in the middle! Source code on my GitHub repo