Industrial Training




Rust Recoverable Errors

    Recoverable errors are those errors which are not very serious to stop the program entirely. The errors which can be handled are known as recoverable errors.
    It is represented by Result< T, E>. The Result< T, E> is an enum consists of two variants, i.e., OK< T> and Err< E>. It describes the possible error.

OK< T>: The 'T' is a type of value which returns the OK variant in the success case. It is an expected outcome.
Err< E>: The 'E' is a type of error which returns the ERR variant in the failure. It is an unexpected outcome.

/

 Enum Result< T,E>  
{  
    OK< T>,  
    Err< E>,  
}  

    In the above case, Result is the enum type, and OK< T> & Err< E> are the variants of enum type where 'T' and 'E' are the generic type parameters.
    'T' is a type of value which will be returned in the success case while 'E' is a type of error which will be returned in the failure case.
    The Result contains the generic type parameters, so we can use the Result type and functions defined in the standard library in many different situations where the success and failure values may vary.

Let's see a simple example that returns the Result value:
  use std::fs::File;  
 fn main()   
{  
     let f:u32 = File::open("vector.txt");  
}  

Output:

In the above example, Rust compiler shows that type does not match. The 'f' is a u32 type while File:: open returns the Resulttype. The above output shows that the type of the success value is std::fs:: File and the type of the error value is std::io:: Error.

Note:


  • The return type of the File:: open is either a success value or failure value. If the file:: open succeeds, then it returns a file handle, and if file:: open fails, then it returns an error value. The Result enum provides this information.
  • If File:: open succeed, then f will have an OK variant that contains the file handle, and if File:: open fails, then f will have Err variant that contains the information related to the error.

Match Expression to handle the Result variants.


Let's see a simple example of match expression:
use std::fs::File;  
n main()  
{  
   let f = File::open("vector.txt");  
   match f   
   {  
       Ok(file) => file,  
       Err(error) => {  
       panic!("There was a problem opening the file: {:?}", error)  
     },  
   };  

Output:

Program Explanation
    In the above example, we can access the enum variants directly without using the Result:: before OK and Err variant.
    If the result is OK, then it returns the file and stores it in the 'f' variable. After the match, we can perform the operations in the file either reading or writing.
    The second arm of the match works on the Err value. If Result returns the Error value, then panic! runs and stops the execution of a program.

Panic on Error: unwrap()


    The Result< T, E> has many methods to provide various tasks. One of the methods is unwrap() method. The unwrap() method is a shortcut method of a match expression. The working of unwrap() method and match expression is the same.
    If the Result value is an OK variant, then the unwrap() method returns the value of the OK variant.
    If the Result value is an Err variant, then the unwrap() method calls the panic! macro.

Let's see a simple example:
 use std::fs::File;  
  
fn main()  
{  
     File::open("hello.txt").unwrap();  
}  

Output:

In the above example, unwrap() method will automatically call the panic macro and the panic! displays the error information.


Panic on Error: expect()


    The expect() method behaves in the same way as the unwrap() method, i.e., both methods call the panic! to display the error information.
    The difference between the expect() and unwrap() method is that the error message is passed as a parameter to the expect() method while unwrap() method does not contain any parameter. Therefore, we can say that the expect() method makes tracking of the panic! source easier.

Let's see a simple example of expect()
 use std::fs::File;  
fn main()  
{  
     File::open("hello.txt").expect("Not able to find the file hello.txt");  
}  

Output:

In the above output, the error message is displayed on the output screen which we specify in our program, i.e., "Not able to find the file hello.txt" and this makes easier for us to find the code from where the error is coming from. If we contain multiple unwrap() method, then it becomes difficult to find where the unwrap() method is causing panic! as as panic! shows the same error messages for all the errors.


Propagating Errors


Propagating error is a mechanism in which errors are forwarded from one function to other function. Errors are propagated to the calling function where more information is available so that the error can be handled. Suppose we have a file named as 'a.txt' and it contains the text "javaTpoint." We want to create a program that performs the reading operation on this file. Let's work on this example.
Let's see a simple example:


 use std::io;  
use std::io::Read;  
use std::fs::File;  
fn main()  
{  
  let a = read_username_from_file();  
  print!("{:?}",a);  
}  
fn read_username_from_file() -> Result   
{  
    let f = File::open("a.txt");  
    let mut f = match f {  
    Ok(file) => file,  
    Err(e) => return Err(e),  
    };  
    let mut s = String::new();  
    match f.read_to_string(&mut s) {  
        Ok(_) => Ok(s),  
        Err(e) => Err(e),  
    }  
}  

Output:

Program Explanation
    The read_username_from_file() function returns a value of the type Result< T, E> where 'T' is a type of String and 'E' is a type of io:Error.
    If the function succeeds, then it returns an OK value that holds a String, and if the function fails, then it returns an Err value.
    This function starts by calling the File:: open function. If the File:: open function fails, then the second arm of the match will return the Err value, and if the File:: open function succeeds, then it stores the value of the file handle in variable f.
    If the File:: open function succeeds, then we create the variable of a String. If read_to_string() method succeeds, then it returns the text of the file otherwise it returns the error information.
    Suppose we have an external file with a name 'a.text' and contains the text "javaTpoint." Therefore, this program reads the file 'a.text' and displays the content of the file.

Shortcut for propagating the errors: the '?' operator


The use of '?' operator reduces the length of the code. The '?' operator is the replacement of the match expressions means that the '?' operator works in the same way as the match expressions do. Suppose we have a file named as 'a.txt' and it contains the text "javaTpoint." We want to create a program that performs the reading operation on this file. Let's work on this example.


Let's see a simple example.
 use std::io;  
use std::io::Read;  
use std::fs::File;  
fn main()  
{  
  let a = read_username_from_file();  
  print!("{:?}",a);  
}  
fn read_username_from_file() -> Result   
{  
   let mut f = File::open("a.txt")?;  
   let mut s = String::new();  
   f.read_to_string(&mut s)?;  
  Ok(s)  
}  

Output:

In the above example, '?' operator is used before the Result value type. If Result is OK, then it returns the value of OK variant, and if Result is an Err, then it returns the error information.


Difference b/w '?' operator & match expression


    The errors which are used with the '?' operator moves through the 'from' function and the 'from' function is defined in the from trait in the standard library.
    When the '?' operator calls the 'from' function, then this function converts the error type into the error type defined in the return type of the current function. If no error occurs, then the '?' operator at the end of any function returns the value of OK, and if the error occurs, then the value of Err is returned.
    It makes the implementation of the function simpler.

Chaining method calls after the '?' operator


We can even shorten the code of a program more by using the chaining method calls after the '?' operator.


Let's see a simple example:
 use std::io;  
use std::io::Read;  
use std::fs::File;  
fn main()  
{  
  let a = read_username_from_file();  
  print!("{:?}",a);  
}  
fn read_username_from_file() -> Result   
{  
    let mut s = String::new();  
   File::open("a.txt")?.read_to_string(&mut s)?;  
   Ok(s)  
}  

Output:

Program Explanation

In the above example, we have chained the call of read_to_string() to the result of the call of File::open("a.txt")?. We place the '?' operator at the end of the call of read_to_string(). It returns OK value if both the functions, i.e., read_to_string() and File::open("a.txt") succeeds otherwise it returns the error value.


Limitation of '?' operator


The '?' operator can only be used in the functions that return the Result type value. As the '?' operator works similarly as the match expression. The match expression works only on the Result return type.
Let's understand this through a simple example.


use std::fs::File;  
fn main()   
{  
    let f = File::open("a.txt")?;  
}  

Output:



Hi I am Pluto.