Try Conversions
The effective expansion of ? is a little more complicated than previously indicated:
1. expression?
works the same as
1. match expression {
2. Ok(value) => value,
3. Err(err) => return Err(From::from(err)),
4. }
The From::from call here means we attempt to convert the error type to the type returned by the function. This makes it easy to encapsulate errors into higher-level errors.
Example
1. ```
use std::error::Error;
```
2. ```
use std::io::Read;
```
3. ```
use std::{fmt, fs, io};
```
5. ```
#[derive(Debug)]
```
6. ```
enum ReadUsernameError {
```
7. ```
IoError(io::Error),
```
8. ```
EmptyUsername(String),
```
9. ```
}
```
11. ```
impl Error for ReadUsernameError {}
```
13. ```
impl fmt::Display for ReadUsernameError {
```
14. ```
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
```
15. ```
match self {
```
16. ```
Self::IoError(e) => write!(f, "I/O error: {e}"),
```
17. ```
Self::EmptyUsername(path) => write!(f, "Found no username in {path}"),
```
18. ```
}
```
19. ```
}
```
20. ```
}
```
22. ```
impl From<io::Error> for ReadUsernameError {
```
23. ```
fn from(err: io::Error) -> Self {
```
24. ```
Self::IoError(err)
```
25. ```
}
```
26. ```
}
```
28. ```
fn read_username(path: &str) -> Result<String, ReadUsernameError> {
```
29. ```
let mut username = String::with_capacity(100);
```
30. ```
fs::File::open(path)?.read_to_string(&mut username)?;
```
31. ```
if username.is_empty() {
```
32. ```
return Err(ReadUsernameError::EmptyUsername(String::from(path)));
```
33. ```
}
```
34. ```
Ok(username)
```
35. ```
}
```
37. ```
fn main() {
```
38. ```
//std::fs::write("config.dat", "").unwrap();
```
39. ```
let username = read_username("config.dat");
```
40. ```
println!("username or error: {username:?}");
```
41. ```
}
```
This slide should take about 5 minutes.
The ? operator must return a value compatible with the return type of the function. For Result, it means that the error types have to be compatible. A function that returns Result<T, ErrorOuter> can only use ? on a value of type Result<U, ErrorInner> if ErrorOuter and ErrorInner are the same type or if ErrorOuter implements From<ErrorInner>.
A common alternative to a From implementation is Result::map_err, especially when the conversion only happens in one place.
There is no compatibility requirement for Option. A function returning Option<T> can use the ? operator on Option<U> for arbitrary T and U types.
A function that returns Result cannot use ? on Option and vice versa. However, Option::ok_or converts Option to Result whereas Result::ok turns Result into Option.
