- In the previous session,
I showed the Option enum type.
Typically used in a function that may return a value
or may return no value.
There's a similar enum type called Result,
where it represents either a value or an error.
So the result type is actually
has two generic type parameters and two variants.
When you create a result object,
it'll either have the Ok variant,
in which case it represents a successful outcome
and it'll have a value of some type T,
which might be an integer or string,
or whatever type you are trying to return.
Or, if you want to indicate an error,
then you'd set it to this variant,
and then basically it contains some kind of error object,
like an exception in other languages, okay?
So, Result is useful in functions which might fail.
So if you try to connect with database,
it could give you back a connection,
or it could give you back an error indicating
that it's not possible.
So the Result enum type represents
either a successful value.
If the variant is Ok,
you'll get back a value of some type.
Or, if the variant is error or Err,
then it represents an error type.
Basically some kind of error object
which will describe what went wrong.
So, this is actually how Rust deals with errors.
Most languages you might have come across
like Java, and C#, and C++, Python have exception handling.
We have it at a try-catch block.
Rust doesn't have that.
It doesn't have a try block or a catch block,
or finally, when a function could fail,
it doesn't throw an exception.
It returns a Result enum,
and the Result enum will be set to be Err,
and it'll kind of indicate what return type it is.
So, the flow of control
in the Rust application is actually simpler.
When you call a function that fails,
it'll return a result.
You can check the result to see whether it's okay
or whether it's an error.
Much more kind of deterministic, much easier to see the flow
in the Rust application.
So like I said,
the Result enum type is typically used for a function
that may succeed with the Ok status,
or it might fail with the Err status
depending on the variant that's set inside the result.
It'll tell you whether it worked or whether it failed.
A good example from the standard library,
this is used a lot in the standard library, the result type,
there's a function called from_str_radix.
You give it a string, and later on in the course,
I'm gonna have to explain the difference between str,
that you see here,
and string, that we've seen as well.
Technically, string is the primary data type
to represent text
as a structure,
and str is a slice part of an existing string,
like a viewport.
A str is like a viewport
or slice into an existing string object.
So that's a little bit complicated.
So we'll discuss that later.
So for now we'll just kind of blur the eyes a little bit,
and I'll say, when you call this function,
you can pass in some text.
Basically a string representing numbers
like 1, 2, 3, 4, 5 as a string,
and a radix, or radix, is the base,
like Base 10, or Base 16 for hex, or Base 8 for octo.
And what the function attempts to do
is to parse this string,
and give you back a number.
You pass in a string.
You pass in something like "123,"
like that, and you pass in radix, or radix, like 10.
And hopefully what it'll do is give you back an Ok result
of type i32, like this.
Okay, but, of course, it could fail.
When you pass in strings,
imagine the user pass in something that wasn't possible,
like "wibble."
My favorite word.
Okay, if you pass in an invalid numeric string,
it's gonna fail.
In that case,
the result that comes back will be this variant.
You'll either get back an Ok result,
or you'll get back an Err result
to tell you that it failed.
So, there we go.
When you call the function, it'll return a result.
The variant will either be the Ok variant,
in which case you can get the number that came back,
like "123," or it'll be the Err variant,
in which case you can get back the error object,
which is effectively like an exception
in other languages.
Right, so this is how it would look.
Have a look at this example here.
I've declared a variable res.
It's a result that may contain an integer 32,
or it might contain a ParseIntError.
From a technical language point of view,
std is the standard crate in Rust,
num is a module, and ParseIntError is a structure,
a bit like a class, defined in that module.
A Java developer might say it's like a class in a package.
A C++ developer might say it's like a class in a namespace.
And a Rust developer would say it's a structure in a module
in the standard library, the standard crate.
Anyway, that's the type of error that might come back,
and that's the type of number
that might come back if it was okay.
Upon the i32 type,
from_str_radix is defined in the i32 type.
Technically, this is a static method in the i32 structure.
Anyway, we call from_str_radix.
It's a bit like saying Int.parseInt
in other languages.
We give it some string,
like some kind of hopefully numeric string.
I mean, that's the idea of it, something like that.
It's what we passed in.
And we're gonna get back a result object,
which may be the Ok variant,
in which case effectively the result that we get back.
I just tidy up my picture.
If the result is Ok,
then what we'll be getting back is the i32.
But the result could be an error obviously,
in which case the error we get back will be that type.
Right, so which we can then display the number like "123,"
or we can display the error like ParseIntError
on the console.
Okay, so this is how you do the equivalent
of exception handling in other languages,
in Java, in C++, in C#.
You'd have to try-catch block,
and you'd catch a ParseIntError.
In Rust, you just call the function,
and it returns the value.
That value might be an error,
and then you can check it, like this.
There is a bunch of methods defined in the Result enum,
like unwrap_or.
We've seen something similar to this.
Very similar, in fact, with the Option enum type.
When you have a Result enum,
you can attempt, you can call the unwrap_or
if the result contains an actual value.
It'll give you back the value.
Otherwise, it'll give you back the default value
that you supply.
When you call this function,
you pass in a default value,
and that's the value you'll get back
if it was an error.
Right.
So, it would look like this.
Here's some kind of result object.
It may contain hopefully an Ok integer,
or it could contain an error.
Attempt to unwrap it,
and give me back the value as an i32.
If it failed, then this will be the replacement.
Instead of getting an actual i32 out of the value,
we'll get this value out of it instead
because it was actually in an error state, okay?
So basically suppress the error
and give me back that value instead.
Right, so unwrap_or is one function available,
but there are many other functions or methods also available
in the Result enum type.
So if you look at the documentation,
go into the documentation for Rust online
in the standard crate,
in the result module.
That's where the Result enum is defined.
So let's take a look at that.
Here we are.
This is the documentation in the Rust online documentation
for the standard crate,
for the result module, which is like a namespace, remember?
Here we are, in the std result module,
and you can kind of scroll through
some narrative explanation of what's going on here.
Some syntax we haven't covered yet obviously, but we will.
Let's click on the Enums link here.
And at the bottom,
you'll see there's an enum type called Result.
Yes, that's what we're looking for.
Let's click there.
So this is the Result enum type
defined in the result module in the standard crate.
It's an enum as described.
It has the Ok variant,
in which case it has some value like a i32,
or the Err variant,
in which case you're gonna get some kind of error object.
And these are the various methods you can call
upon a Result enum.
Okay, so the one we just looked at was unwrap_or.
Okay, so it kind of explains the usage.
The other functions here,
obviously you can have a look at as well.
So do spend a while looking through these functions
when you get a moment.
Right, let's take a look at an example then
in the lesson04_enums project.
We're gonna look at the demo_using_result_enum,
and then we'll run the application and see how it looks.
Right, here's my lesson04_enums project.
This is the last time we're gonna look at this
in this lesson.
In main.rs,
let's uncomment the code
for that function demo_using_result_enum.
Very good.
It is here.
Let's have a look.
Oh, it's here.
Here it is.
So, I declare a res variable of type Result.
It could contain an Ok i32, or it could be an Err,
in which case the error that you'll get back
when you call the parse function
and from_str_radix function.
The error type that it returns is that error type.
That's the error object you could get back.
So, let's see.
First of all, I call the function.
I pass in a valid hexadecimal string.
FF is
255, I think, as an actual number.
So this res will have the Ok variant,
which should get extracted into n.
And I'm thinking it should be 255.
Let's give that a spin.
My hexadecimal mental calculations are not legendary.
Let's hope I got that right.
Cargo run.
We've got those warnings about functions at the top.
So I think if I give it FF,
that is a valid hexadecimal string.
So all good here.
It parsed the string.
Basically it returned an Ok, and the Ok value was an i32.
That's the value that we'll extract.
Pattern matched.
Extract the value from the Ok variant if you like,
and output it.
And it's 255.
And then at the end,
here I've got res2.
I've parsed res2 as an integer, like that.
And that's gonna work, okay?
So that will also be unwrap successfully as 255.
So that's good.
And then what I'll do here is,
I'll kind of comment that one out,
and I'll uncomment that line.
This is clearly an invalid string.
So that's gonna fail,
but it'll return a result
with the Err variant.
And I'll come in here,
and I'll basically extract the Err variant,
and it'll print the error message there.
And similarly here,
if I give it an invalid string here like "wobble,"
wibble wobble,
jelly on a plate,
when it attempts to extract, that's gonna return result.
It will try to unwrap it.
It fails basically because there's an error,
and it'll give me back a value -1 instead.
It kinda swallows the error,
and give me back a default value instead.
Right, so this is gonna be the last demo
we're going to run hopefully in this lesson.
Let's hope it works.
Fantastic.
So when I gave it an invalid string,
it returned in a result that was an error state,
and it matched this pattern,
and it says an error occurred,
and then it printed the error object.
So the error occurred, and this is the error object.
Error objects have some kind of string,
like a message to say how it went wrong.
So when you print an error message, an error object,
it'll give you back something meaningful.
If you try to unwrap a result that's in error,
it'll just give you back the default value
that you told it to.
It fails gracefully,
because maybe -1 would be good enough in our scenario.
So the unwrapped result now would be -1.
Okay, so that concludes the lesson.
We've looked at enums.
We've seen how enums can convey data.
We looked at the Option enum
that can contain a value or none.
And then finally, we've looked at the Result enum
that can contain a value or an error message, okay?
And in the rest of the course,
we're going to be using enums as appropriate
to indicate errors, optional values,
and other state machines of our choosing.