 So, I've been given this talk about Ruby and exceptions and failure handling for a while, and I thought, since this is a very special conference, I'd do something a little bit different this time. So today, I just wanna go through a few things that you may not have known about exceptions in Ruby. And my goal today is that everyone here will go away having learned at least one thing about exceptions. So just to get things started, please raise your hand if you have not yet learned anything new about exceptions. Everybody? Okay, just checking. All right, let's get started. Ruby is one of the few languages where the exception mechanism has a retry feature. So, if you've ever had a piece of code where you need to try something over and over again, like maybe you've got a flaky web service that you're hitting or something like that, you might have written something like this. So here's a simple loop that's trying something over and over and it has a threshold past which it's not gonna try anymore and it's just gonna give up. Here's a version of that using retry. So here, we're defining a tries counter and then we're trying something out. It raises an exception and in the rescue, we check to see if we've exceeded our tries counter. If not, we say retry and what retry does is it sends execution right back to the start of that begin block. We keep doing this until we hit our max and then we say forget it. All right, has anybody not learned anything new about exceptions? Good. The dollar bang global variable. What the heck is the dollar bang variable? Well, this is a global that Ruby keeps and it always contains a reference to the exception that is currently being raised, currently being handled and if there is no exception being raised, then it is nil. It is also known by the alias error info if you include or if you require the English module. So here's a simple example of using it. What this demonstrates is simply that before an exception is raised, that variable is nil. While it's being handled, while it's being rescued, that variable is set to the current exception and then once it's been handled, that variable is once again nil. This is useful for a few things. Here's one thing it's useful for. Have you ever had a case where you wished you could add some kind of crash logger to an application? So you wanna catch exceptions that are not handled and they're just bubbling out and causing the program to end. But for one reason or another, it's not convenient to put a big old begin rescue end around the entire program. You wanna just sort of plug a crash handler in. Well, you can if you use an at exit block. At exit is normally executed when the program ends. But how do we know if the program is ending normally or if it's ending because of an unhandled exception? Well, that's where this dollar bang, this error info variable comes in. We can check that and say, oh, this is ending because an exception was raised. So we're gonna log some information about it. In this case, we're logging the time, logging some stuff from the exception itself. We're also logging the version of all the gems that were loaded at the time of the failure. I'm sure you can think of lots of other fun stuff that you could put in a logger like this. Something else we can use the global error info variable for is nested exceptions. Nested exceptions, if you're not familiar with them, is just the idea of an exception which has a pointer back to whatever exception spawned it. So if you have a case where you rescue one exception and then you raise another one, nested exception has a pointer back to it. Ruby doesn't have these built in, but it's very simple to define a nested exception class. Here's an example. It's just a few lines of code. We just add that original attribute to the class and have a way to set it when we raise the new exception. So here's an example of using that. We say we raise error A and then we rescue it. And then we raise error B and then we rescue that. And then we inspect the resulting error. And what we see is that the exception that we rescued is error B, but it also has a reference to error A. Now wait a minute. We didn't actually set that reference. We didn't say error B, here is your reference to the original. How does this work? Well, if you look at how this class is defined, the default for that original attribute is error info. And so what this has done is it has sort of automatically picked up the current exception from the environment where it was created. Is there anybody here who has still not learned anything new about exceptions? Come on, there we go. All right. Ruby lets us re-raise exceptions if we decide that we can't handle them. And, but something that's used less often is the ability to modify the exception. So here's a case where we are loading some data up. We're just, we're going through a file line by line and we're parsing some data out of it and putting the data in an array. And if that fails somewhere along the line, we get this completely inscrutable exception that if the user sees that, they're like, what compiler? What's that about? Where did that come from? Where in, even if they know it was coming from a file, where in the file did it come from? So what if we put a begin rescue end around that and we rescue the exception and then we re-raise the same exception, except all we do is we modify the message to insert a little bit of context about where it was raised. So we're saying raise the exception with this new message which includes the old message. Now, when they get that error, they can see this contextual information at the start of the message. But all the other state on that exception is preserved. So if there's any special attributes on that exception, they're all preserved. The backtrace is all preserved. The only thing that has changed is the message. This is a great way to add some extra contextual information that maybe wasn't available at the point where the exception was originally raised. Has anybody still not learned anything new? I could just stop right here, but I'm gonna keep going. One of the things that surprises a lot of people coming to Ruby is how many of the keywords really aren't keywords at all. They're methods. Raise is one of these. Raise is just a method on kernel. And what this means is we can override it. We can write our own raise. So here's one example, kind of a silly example. Let's say we're tired of all this exception business and we just want errors to be instantly fatal. Well, here's a way to make it so. This version of raise just prints out the exception message and then terminates the program. Not saying you should do this, but you could. For a potentially more interesting use of this fact, you might wanna take a look at my Hammer Time gem. Hammer Time is an attempt to add a Lisp or Smalltalk style error console to Ruby. So at the point where the exception is raised, it will give you the option to ignore the exception, to continue raising the exception, to drop into a debugger at the point where the exception was raised, drop into a console, a bunch of other stuff. More? All right. So when you look at how you raise exceptions in Ruby, you might think that it's working something like this, like the default raise is working something like this. You pass it an exception class, you pass it a message and it's saying exception.new with a message and that's how you get your exception object. Not actually how it works. What it's really doing is it takes the thing that you pass to it and it calls this method called dot exception on it and it passes in your message to that. So dot exception, you can kind of think of as a coercion method like 2s or 2a or 2i, except what it coerces is it coerces things into exceptions. It is the way Ruby gets an exception object from something else and by default, it's only defined on the exception class so you've got a class level version which is equivalent to calling exception.new and you've got an instance level which may return the same object or it may return a duplicate modified object depending on how you call it. But that doesn't mean, just because that's the only place that Ruby defines, it doesn't mean we can't define our own. So imagine that you wanted to expose an API where you wanted to let the callers decide whether a particular response warrants an exception or not. So you don't want to raise exceptions yourself, you just want to let them decide. But you want to make it easy for them to raise an exception if they do decide that it warrants an exception. Well, here's one way you could do that. You could add dot exception to that response object and then if the caller decides that yes, this is exception worthy, they can just raise that response object and internally raise will say dot exception, it'll convert that thing into an exception and now what you're doing is you are delegating the responsibility of what exception object to construct and what information to put on it to that response object. More? So when you look at the way that Ruby rescue clauses are structured, you might see something kind of familiar. It looks a lot like Ruby case statements. And you have a list, a single class to match on or you have a list of classes to match on and this is not a coincidence. They are actually implemented very similarly. And among other things, what this means is just like with case statements, we don't have to have the list of things to match hard coded. So if we want to, we can expand that list at runtime. So here's an example where we define an ignore exceptions method where we pass in a list of exceptions that we want to ignore and then we wrap the piece of code where we want to ignore those exceptions in this block. And what it does internally, it says rescue star exception. So what it's doing is it's splatting out the exception list and passing that to rescue. And you can see, here is an example of using it. We say ignore IO error, ignore system call error. I don't actually recommend ignoring these errors. And they are ignored. So that's all fine and good, but we can get even crazier with rescue. Remember I said that rescue is implemented very similarly to case statements. And among other things, what that means is rescue, just like a case statement, rescue uses the three equals operator, the triple equals, to decide whether something matches. Now it puts one arbitrary proviso on there. The matcher, it has to support three equals, but it also has to be a class or a module. But as long as it's a class or a module, it doesn't care, it'll just call three equals on it and that'll decide whether the rescue matches. So we could generate our own anonymous modules that define custom three equals operators. Here's one which just defines the three equals is doing whatever the block contains, which is probably not as clear as mud, but hopefully this will clarify what we're doing here. Let's say we have this notional retrieable exception, or a retrieable error, which contains a count of the tries that this operation, the times that this operation has been tried. We can use this error matching method that we defined in the last slide to define a dynamic way of matching exceptions. We say match any exception where the number of tries attribute is less than three. So should we stop there or do you wanna see more? Thank you, Jeff. System exit. When you call exit in Ruby, what you're really doing is raising an exception, which you can see in this piece of code here. We say exit and then we rescue any exception and we ignore it and the program continues lively along despite the fact that we told it to exit. So you can force programs not to exit this way. Abort is the same way. Abort just sets up the system exit exception to have an error status instead of a success status, but other than that it's still raising system exit. If you really want to exit the program immediately, you have to call exit bang and that will just cut it off right there. It won't even execute add exit handlers or anything like that. But other than that, you're actually raising an exception when you say exit. So there's gotta be somebody in here who has read my book and still hasn't learned anything. There we go. All right, this isn't in the book. So in Ruby, if you really don't like an exception, you can just ignore it. So here's a whiny method that raises an exception and it's a stupid exception. We don't really care about it. So what we'll do is we'll say call the whiny method and rescue the exception and then when we rescue it, we'll just call ignore on it. And what you can see is that, remember we defined this with before raising and then after raising. After raising should never be hit, right? Because we raise an exception there. Well actually, it is hit because we decided to ignore the exception. Now some of you are probably going wait a minute, that's not in Ruby. All right, so I lied. But it's not that hard to implement. So we'll start with a modification to the exception class, the basic exception class and we're just gonna add an attribute to it which will hold a continuation and we'll get into what a continuation is in a minute. And then we're gonna define this ignore method which we'll simply call the continuation. So keep that in your mind. Now here's the meat of it. Here is a redefinition of raise which surrounds the call to the original definition of raise in a call to call CC. Now call CC is call with current continuation. A continuation is, you can think of a continuation as like when you're reading a book and you realize that you need to reference something that you had, that you already passed. And so you stick your thumb where the page where you were reading and then you flip back to where you were and then when you're done, you flip back to where you put your thumb in. You can think of the continuation as that thumb in the book. So it's holding the place where this raise was called. And what we do inside that is we delegate back to the original Ruby definition of raise and then we catch the exception that comes out, we tack on our continuation and then we let it continue. Then we just need to add this to objects so that this is now our default definition of raise. And here's that usage of it again. We rescue the exception. It has that continuation on it now and when we say dot ignore, it's ignored. It jumps right back to that place in the code where rescue was called and continues as if no exception was ever raised. Now, this is something to probably be careful with, especially in production apps, but play with it. You can do some really fun things with continuations and fibers in Ruby. So hopefully one of those was something new to you. I actually have a fair amount of time left so I can take some questions. Yeah, I forgot about that slide. So yeah, I don't have anything more. But before we continue on, are there any questions? I will assume that I have blown your mind. All right, well, if you wanna dig into more fun stuff like that, I wrote a book about this stuff. There's a discount code there that you can use and if you would, I have one little favor to ask you. My wife Stacy makes it possible for me in a thousand different ways, unsung ways makes it possible for me to go to all of these conferences and do these talks. So if you would just tweet, thank you to Bully Chic, I would really appreciate that. I'm gonna get a freaked out call in a minute. Oh, there are, I didn't see the hands. Yes, we're here. So the question is we know that it's generally frowned upon to use exceptions to manage flow control when it's not an exceptional case and have I ever used throw and catch to do that instead? And yes, the answer is yes. And Ruby is a wonderful language because it gives us throw and catch to handle those non-exceptional cases where you wanna sort of toss execution way up the call stack and then move on. You can see some great examples of that in like Rack and Sinatra, well any Rack app really, where it uses that to halt the request early. So yes, definitely use throw and catch if you have a case where you wanna do something sort of exception-like, but it isn't really an error case. And are there any other questions? I'm clearly having a hard time seeing hands so feel free to just yell out. So the question is was the Hammer Time stuff in preparation to do a condition-like, Lisp-like condition system? And yeah, I was doing a lot of thinking about Lisp-like condition systems when I put that together and while I didn't take it to that extent, somebody has actually released, I think it's called Kond, but I could be wrong. Somebody has released a gem which implements what appears to be a pretty full Lisp-style condition system. So if you've ever felt like Ruby exceptions are just not powerful enough, look that up. I think I have a reference to it on like my talk notes page. It's a neat-looking gem. Is there a cost to throwing exceptions over time? When you say like the cost, do you mean like a one-time cost in how long it takes to throw the exception or like something that builds up? I would not say that there's a cumulative cost to raising exceptions, not in my experience now. One note about that, this business with continuations and ignore, I don't know what the situation is now. In the past, Ruby had some memory leak issues around continuations, so that's another reason to just sort of tread carefully if you play with that stuff. Have I found anything in Ruby that simply fails silently and doesn't raise an exception? Well, periodically, Ruby core dumps. So in that sense, yes. But other than that, I haven't seen anything in the standard library that has that pathological behavior. I tried to use these methods in the JRuby runtime and what kind of performance cost do they have? I have not profiled any of this stuff on JRuby. In general, in any programming language, anything that's unrolling the stack has a performance cost to it. Probably more of a work, for me, it's more of a worry with throw and catch because that's something that you use during the normal progression of a program. Raising exceptions is by definition something that shouldn't happen very often, so I generally don't really worry about the runtime cost of them. Anything else? So I don't wanna tell you the wrong thing off the top of my head and I don't have it off the top of my head. If you look for my talk notes page, just like Google, Opti, and Exceptional Ruby, you'll probably find the talk notes page. You should find a reference to it and if I haven't put it there, I will make sure I put a reference to it there by the end of the day, going twice. Thank you so much for listening.