Check out a free preview of the full Polyglot Programming: TypeScript, Go, & Rust course

The "Error Handling" Lesson is part of the full, Polyglot Programming: TypeScript, Go, & Rust course featured in this preview video. Here's what you'd learn in this lesson:

ThePrimeagen briefly walks through the error handling in JavaScript and TypeScript and live codes error handling in Go and Rust. In Go, errors are pointers under the hood, so they return the empty type. Rust can handle errors using enums (sumtypes) with the Result type.


Transcript from the "Error Handling" Lesson

>> So error handling is the exact same way they're solving it, except for instead of having a one value generic or one type generic, it actually has a two type generic. So with JavaScript it does this thing called effectively exceptions as control flow, but it also handles it via returns, returning an error.

Classic JavaScript conflating issues as always, right, null, undefined, right, it has all these fun things that it does. So in JavaScript before promises, before you use promises, often it was just a callback, a callback to return an error as the first argument, the value as the second argument.

But if the function threw an error, it meant something different than if it returned an error, right? JavaScript has always had this kind of conflation problem where throwing as a control flow plus returning is also part of it. So it's kind of a harder thing I'd say, to grapple with errors in TypeScript in general.

They feel more easy now cuz everyone kind of agrees that promises or things that contain the error and then return it out to you isn't much easier. But you can still get into this weird state where, say, you had some a sync call that goes off on the Internet and grabs a value and brings it back in.

If you bring it back in and throw an exception, if no one's listening to your exception, does anyone hear it in the woods? No, of course not, right, because it just gets thrown and now you have this error popped up in the console but there's not anyone handling it.

And so it's always harder, I would say, with JavaScript. I was gonna go over this great example right here, I think everyone kinda gets it, Goodbye, World, you call foo, you console log, we had a problem but we are okay, then great success, right? And we have .catch, we kinda know those things.

I'm just kinda speeding along a little bit right here. So Go, it's different. It's both an argument for and against Go at the exact same time, how they handle errors. People hate them, but at the same time I still think they're better than how they're done in JavaScript.

Effectively let's kind of, let's see, do I just, okay, good. I go over the code right there. So I'm just gonna go over the code really kinda quickly here cuz I feel like Go, since there's less familiarity here, we should probably kinda go over that. So in this world, we can do things like this.

And we just half that. I can create a function that's like returnsError. And this thing is gonna return a type error. So I can return a new format, and go Errorf(This is an error with value), and I'll make this thing, say, take in a int. And we'll do that.

And I'll take in the value and look at that, we have this beautiful little error that's returned from this function. Now Go treats all errors like values, right? There are panics, you don't wanna panic to happen cuz panics just destroy the program, right? It's where you cannot handle the error at all.

It would be equivalent to the case of an error being thrown in some async callback in which nobody handles, right, in JavaScript. It just terminates the programs and that's terrible. There is apparently some way to avoid some level of termination, but we're never gonna be getting into that today.

So if I were to do this, I could do something like this, err := returnsError. And I can give it a value, say 5. Now I have this error, I actually have the error. So this means that if we were to have something that looks like this struct Foo, right?

So some class, right, some struct out there that has some basic variables, oop sorry, type struct, can't get those backwards. So in Go that's how you specify struct. But I had some sorta struct that had some values and it had a function on that struct. So in TypeScript you put it on the class, in Rust you do that input call, in Go it's even a little bit more kind of goofy.

You can say something like this, go function then you can add it on there and then you go thisIsOnFoo, right? And this can return also an error and do the exact same thing here. Let me just grab that 6 down and paste that up there. And so I could do the exact same thing, err =, or actually foo := foo and then go foo, and as you can see there's an error right there, and I could actually get the error out and this thing would be an error, right?

More so when you're creating structs and there is a potential for error. Let's kinda go over that cuz I feel like this is very, very important to understand. How about this one, CreateFoo. By the way, if it starts with a capital letter, it's exported in Go. I think it's one of the tragically best and worst concepts ever cuz it feels nice but at the same time, man, there's something that just kills me on the inside when I don't wanna export a type.

I have to have a lowercase and it just, I'm emotionally bruised by that, right? Some people get emotionally bruised by other things, I get it with capital letters, right, it just really hurts me. So let's say we're just gonna create a foo and there's a chance that this thing could error.

So how do we return this? Well, if we did this, (Foo, error), let's go over what would happen. Let's say that if, we'll add a little number here (bool, fail), right, oop, the other way round (fail, bool), there we go. So if we were to fail, we should return the error.

So that means we have to return a foo object and then the error, right? So then I could just go up here and I could just grab this thing really quickly. There we go, right, how beautiful was that? Oopsies. Hey, where are you? Errorf, we just use Errorf, whatever.

There we go, it's good enough, but there we go. So that returns an error but what's the problem? We have to create this thing every single time. So what you'll often see, In the Go world is anything that throws an error always comes as a pointer, right? And what this means is I have the magical ability to call it nil, but I have to turn it into a pointer on this side.

And so this is pretty much error handling in Go, you see it all the time. Because you don't wanna have to create the object every single time there's an error, right? You just wanna return the error and that is it. There's also an errors package that I could use and just go if I didn't wanna do this.

But either way, it's the same kind of concept, is that we have this ability to return either an error or the value, and Go always makes you handle this. And I think this is actually a pretty good construct. So I do something that looks like this, right? CreateFoo(false), right, and then I do something that looks like this.

Don't use semicolons. Is that I have this actually built on a hotkey for me. So if I just go like that, it just makes one happen. Cuz I just use this that often, right? And then I also have one for the main function, which of course is, oopsie, space ef.

No, that's on my other VMRC, I didn't commit it, dang it, but I have it so, I didn't commit it from my other machine. But either way, you'll see this so often that I actually just have this hotkeyed out. Is it annoying? Yeah, it's annoying because every three lines you have this if error does not equal nil, handle the error case, but the nice part is is that you constantly have to think about errors ahead of time.

There's no like, this method throws an error, I should handle this. It's like no, you have to do it. There is no getting out of it. I do think that it's a nicer construct to have values errors. Personal opinion, hashtag hate me for it. There you go. So we got this.

Does that all make sense with Go? Go is really simple, so I'm not gonna spend a huge time kind of arguing. I think everybody, if you're familiar enough with TypeScript, nothing in here is confusing other than this, right? So Go is a magic place, you can return pointers and it still works out, kind of a weird world.

If you come from C, you'd never create something in a function and then return a pointer to it. Cuz it gets destroyed in that function and then you're still pointing to it and that's considerably no. That's bad news bearers, things always go wrong that way. But in Go you can do that cuz it actually has an underlying engine underneath that, make sure everything's living correctly.

So it just simplifies programming. All right, so that that makes sense, right? Here you go. Here's all me doing those things, right, we talked about that. Nice try, guy, it's a great phrase. There we go. So now with Rust we're gonna use an enum for error handling, and I think this is where it gets super cool, which is that we already understand the enum, right?

It has two generic parameters, a value and an error. And it has a type error for error or err for the error, right, err, and ok for the value. That means if your function has a possibility of throwing an error, it should return a result. The result would then either be an error or not an error.

So you always have to handle it at that point. But Rust became so, obviously errors are so often, that it even added syntax to make this a lot easier. And the result object implements the dot map function. And that's very important because we'll see why here shortly, why that's so awesome with options.

Same thing with errors is you can use flat maps with them. I'm gonna jump back here really quickly. And let's just do a little fun function called error_me. By the way, Rust also forces you to write camel case, all right not camel case, snake case. I hated it, I absolutely hate it, and and now I love it.

It's funny how you change your style then you think it's better. I don't know how that happens. But I'm easily manipulatable apparently, all right. So we're gonna go like this, we'll have throw as a bool, and we are gonna return a result of, we either choose a value.

So this is the empty value. This just means, hey, there's a thing there, it's just nothing, it's a unit, it's just what it is, or we may return some sort of error. Let's, for whatever reason, we're gonna return a type of usize as an error. usize is just a, all it simply is, oopsies, a usize is just unsigned integer, the width of your computer.

So if you have a 64-bit computer, it's likely 64 bits. Depends on the architecture, if you're running on 16-bits, 16 bit. A usize is just the width of your computer. 64 bits, that means you get a 64-bit integer. So it's used for indexing arrays and all that. So right now I'm just simply getting a result.

Now it's kinda weird returning a value as an error, something that you wouldn't consider an error. But remember, the error object is simply, it's just a type, right? It doesn't have any specific constraints on it, there is an actual error trait that you can implement. A trait is just an interface, is how you can think of it.

So there is actual error errors but in this case we're just using something really simple. I'm just returning a value, right? So if throw, then return an error of, I don't know, 7, right? Else return an Ok of nothing, right? And so there you go. I actually just had an error and here's the error case, here's the good case.

And so when I call this, this is where things kinda get a little bit neat. So what I'm gonna do is I'm also gonna return a result. And for the sake of this, I'm gonna keep it the exact same. You can make errors better and they can be more generic, and then you can be able to handle all sorts of different errors.

Right now we're just gonna handle this. Now, this is the cool part. So at the very bottom, I'm first gonna go like this, return Ok, boom. And so there we go, everything's happy. I can call error_ me, right? And say I provide false, I can do this little thing right there.

I can add in a question mark, this question mark effectively attempts to unwrap your error. But if it is an error and not a value, it will return that. So you can effectively think of this right here as the following. It's like let value = match, and then I'll just take that whole thing.

An Err(e), then we're gonna simply return Err(e) or we're gonna give out the value, right? Or we're gonna have, what is it? Ok(v), there we go. And this thing now is the value. If there was a value, it does it, else it returns it out, and so it stops your control flow and hands it up to the next handling function.

And so this is actually pretty. I mean, this is a very nice thing cuz it gets rid of that if error that you constantly have to do, so long as your errors are kind of planned. Anyhow, we'll be going over the anyhow crate here in just one second.

You can always just keep on using this question mark operator, and as long as your function also returns the same type as your question mark operator, it will work. So this also works with options. If you return an option from your function, you can also question mark it and get that same option out.

And so it's pretty cool. It's like an easy way of handling it. I've never used it with the option side, but I've definitely use it all the time with the error side. This makes a ton of sense, makes it really simple. So I can literally just go add value.

And as you can see right here, it's just the empty one that we said right here, right? That's what it is. And so that's pretty awesome. And it's a really great way I think of handling errors, makes it straightforward. So Rust does the same thing as Go, just adds a little bit of syntax to make it easier.

And of course, it has all the other things, right? So if error_me(true).is_ok. I will now okay. Only do something if it's ok, right? So this is good for, say, checking if a file exists or not. I can do something right here. I can say, hey, Go, does the file exist and is it ok?

Is it not an error? It is an error, so that means it doesn't exist. And so we can kinda do those type of things. There we go. Pretty straightforward. Everyone's fine with that. Was there anything else I was gonna show you? I don't think there was. Obviously, there's all the other things, there's unwrap, there's expect.

Expect is kind of like a way you put into your program where if this is an error, your program has to crash. And you can give a little bit of context on it, like hey, it's gonna crash, but here's why it crashed. Say you take a CLI program that expects you to provide at least two parameters for a specific operation and you only provided one.

Well, you can expect it, right? You can just say, hey, this should have happened, you didn't do it, I cannot proceed, so program's done. There you go. There's this really nice library called Anyhow. I did not know about Anyhow for a while, I use this error for a while.

Anyhow is fantastic if you're writing a program. This error should be used if you're writing a library other people will consume. Anyhow is if you're trying to just write a program. So let me just show you a quick little thing with Anyhow. cargo add anyhow. It is beautiful.

So Anyhow, let's see, use anyhow, oop, you always have to do this, LspRestart. Now we wait a moment of time because the Rust analyzer takes a while, it has has a lot of things it needs to do. So anyways, we have the anyhow and it has its own result type.

So you can actually redefine result. We can even do that ourselves if we have the same return type. They do it for us. So I can do something like this. So instead of returning that, I can go like this. Anyhow, let's see. I can do like, ("this should never be true"), right?

Yay, we have this and of course I believe that is because this thing is anyhow. There we go. It's the macro, there we go. And what is its problem here? Err, there we go. I created an anyhow error right here. Or let's just say you throw an error.

There are operations in which throw errors. So if I do fs, this is just a file system. And, I don't know, let's see, can I just do a simple read? Let's go, PathBuf, I'm just gonna read something really quickly, from foo, right? This thing is likely to be an error because it doesn't exist, so it returns an error out, right?

It returns this result object out for you to be able to interact with in case it's an error. So I can use this thing called context from anyhow where if this is an error, I can go, hey, unable to do this. So now I added a little additional context to it so that when my program errors, I have this nice little message that I put in there and then I can unwrap it.

And boom, it still acts the same, doesn't throw the errors. Everything works nicely, and it has this really complex error type that I don't have to worry about how complex it is. It just does it all for me. So anyhow is just magical, right? It's just like, it makes error handling super easy in a typed language.

So this almost feels as loosey-goosey as something like JavaScript without all the painfulness in a type system. So, pretty fantastic. Absolutely love it.

Learn Straight from the Experts Who Shape the Modern Web

  • In-depth Courses
  • Industry Leading Experts
  • Learning Paths
  • Live Interactive Workshops
Get Unlimited Access Now