Cannot move io::Error out of Peekable Result - error-handling

I'm just trying to propagate the IO error:
enum MyError {
EOF,
IO(std::io::Error),
}
fn peek_byte<R>(mut p: Peekable<Bytes<R>>) -> Result<u8, MyError>
where
R: Read,
{
match p.peek() {
None => Err(MyError::EOF),
Some(Err(e)) => Err(MyError::IO(*e)), // <==== error is here
Some(Ok(byte)) => Ok(*byte),
}
}
But, I get the following error:
error[E0507]: cannot move out of `*e` which is behind a shared reference
--> src/main.rs:17:41
|
17 | Some(Err(e)) => Err(MyError::IO(*e)),
| ^^ move occurs because `*e` has type `std::io::Error`, which does not implement the `Copy` trait
I actually understand all of this. I know why I'm getting the error, and what the error means. What I don't know is how to accomplish my task and propagate the IO error into my error type.
I have tried e, *e, e.clone(), *e.clone(), *(e.clone()), but they all either produce a "type mismatch" or a "cannot move" error.

The Peekable iterator holds ownership of the next value of its internal iterator and returns references via peek, but if you actually want the owned value you just call next as usual (which does advance the iterator but I think that's okay in this case since you're not actually consuming any content from the iterator but just trying to return an error):
use std::io;
use std::io::Bytes;
use std::io::Read;
use std::iter::Peekable;
enum MyError {
EOF,
IO(io::Error),
}
fn peek_byte<R>(mut p: Peekable<Bytes<R>>) -> Result<u8, MyError>
where
R: Read,
{
match p.peek() {
None => Err(MyError::EOF),
Some(Err(e)) => Err(MyError::IO(p.next().unwrap().unwrap_err())),
Some(Ok(byte)) => Ok(*byte),
}
}
playground

Related

Conditionally Acting on Specific Error Using and_then in Rust

How do I know what error happened when piping Results via and_then in Rust? For example using nested matches I could do this:
use std::num::ParseIntError;
fn get_num(n: u8) -> Result<u8, ParseIntError> {
// Force a ParseIntError
"foo ".parse()
}
fn add_one(n: u8) -> Result<u8, ParseIntError> {
Ok(n + 1)
}
fn main() {
match get_num(1) {
Ok(n) => {
match add_one(n) {
Ok(n) => println!("adding one gives us: {}", n),
Err(why) => println!("Error adding: {}", why)
}
},
Err(why) => println!("Error getting number: {}", why),
}
}
Or I can use and_then to pipe the result of the first function to the second and avoid a nested match:
match get_num(1).and_then(add_one) {
Ok(n) => println!("Adding one gives us: {}", n),
Err(why) => println!("Some error: {}.", why)
}
How do I conditionally and idiomatically determine the error in the second form, using and_then? Do I have to type check the error? Sure I can display the error like I did above, but let's say I want to preface it with something related to the kind of error it is?
The normal way would be to use map_err to convert the initial errors to something richer you control e.g.
enum MyError {
Parsing,
Adding
}
get_num(1).map_err(|_| MyError::Parsing)
.and_then(|v| add_one(v).map_err(|_| MyError::Adding)
sadly as you can see this requires using a lambda for the and_then callback: and_then must return the same error type as the input, so here it has to yield a Result<_, MyError>, wjhich is not the return type of add_one.
And while in other situations we could recover somewhat with ?, here the same original error type is split into two different values.

Why does the ? operator report the error "the trait bound NoneError: Error is not satisfied"?

The ? operator at line 9 works OK, but if I use the same logic on the same type in line 19, it blows up.
use std::error::Error;
use walkdir::WalkDir;
fn main() -> Result<(), Box<dyn Error>> {
let valid_entries = WalkDir::new("/tmp")
.into_iter()
.flat_map(|e| e)
.flat_map(|e| {
let name = e.file_name().to_str()?; // <-- this works
if name.contains(".txt") {
Some(e)
} else {
None
}
});
for entry in valid_entries {
println!("This file matches: {:?}", entry);
let name_to_str = entry.file_name().to_str()?; // <-- this blows up
// ...
}
Ok(())
}
The errors are a little cryptic for me to interpret:
error[E0277]: the trait bound `std::option::NoneError: std::error::Error` is not satisfied
--> src/main.rs:19:53
|
26 | let name_to_str = entry.file_name().to_str()?;
| ^ the trait `std::error::Error` is not implemented for `std::option::NoneError`
|
= note: required because of the requirements on the impl of `std::convert::From<std::option::NoneError>` for `std::boxed::Box<dyn std::error::Error>`
= note: required by `std::convert::From::from`
Why is the ? operator blowing up while iterating valid_entries?
The ? can be used to check-and-return any type that implements the Try trait (still unstable). The only implementations in std of those are Option<T> and Result<T, E> (plus some Future-related impls that are not relevant to this discussion). This means that you can use the ? operator in any function that returns Result<T, E> or Option<T>.
But you cannot mix-n-match those. That is, if your function returns a Result<T, E> you cannot use the ? in a value of type Option<T>. Or vice versa.
The reason your first ? works is because you are inside a flat_map() that returns Option<String> and all goes well. The second one, however, is in a function that returns a Result<(), Box<dyn Error>> so you can't use ? with an Option<String>.
The solution is simply to deal with the None in your Option<String> in another way:
Doing a match / if let Some(x) to handle the error separately.
Converting into a Result<String, Error> and use ?, for example with .ok_or(std::io::ErrorKind::InvalidData)?;.
Similar to 2, but take advantage of the impl From<&str> for Box<dyn Error> and do .ok_or("invalid file name").
Giving a default value, with Option::unwrap_or() or similar.
Doing unwrap() and panicking if None.
Ok, but what does this error mean? The idea is that actually you are able to use ? with an Option<T> that returns a Result<T, E>, as long as your E implements From<std::option::NoneError>. Unfortunately, NoneError is still unstable, too, so you can't implement From<NoneError> in your code using the stable compiler. Nevertheless, the E in your code is Box<dyn Error>, and the compiler would be happy to do the boxing as long as NoneError implements Error, but...
error[E0277]: the trait bound `std::option::NoneError: std::error::Error` is not satisfied

Rust -- How to handle error precisely and gracefully?

Sorry for naming this a so ambiguous title, but I can't come up with a better one.
The read_dir() method defined in std::fs returns instances of io::Result<DirEntry>, which is the alias of Result<DirEntry, io::Error>. When the caller does not exist on file system, it returns the error.
Now my code is
dir_a.read_dir();
dir_b.read_dir();
dir_c.read_dir();
And dir_a, dir_b, dir_c all may not exist. So these three statements may return the same io::Error, but for my program, dir_a, dir_b and dir_c have different meaning, where I want to handle the error for each one respectively.
So I defined my own enum MyError as
enum MyError {
Dir_a_not_exist,
Dir_b_not_exist,
Dir_c_not_exist,
}
How can I transform the same io::Error to my different three MyError?
My ugly way is to
match dir_a.read_dir() {
Ok => match dir_b.read_dir() {
Ok => match dir_c.read_dir() {
Ok => { /* do something */ },
Err => return MyError::Dir_c_not_exist,
},
Err => return MyError::Dir_b_not_exist,
},
Err => return MyError::Dir_a_not_exist,
};
Is there any graceful way I can handle this?
Result has a function called or, that allows you to forward the result if it's Ok, or transform it if it's an error. With that, you can do something like this:
fn foo(dir_a: &Path, dir_b: &Path, dir_c: &Path) -> Result<(), MyError> {
dir_a.read_dir().or(Err(MyError::DirAnotExist))?;
dir_b.read_dir().or(Err(MyError::DirBnotExist))?;
dir_c.read_dir().or(Err(MyError::DirCnotExist))?;
/* do something */
Ok(())
}

Convert vector of enum values into an another vector

I have the following code which generates a vector of bytes from the passed vector of enum values:
#[derive(Debug, PartialEq)]
pub enum BertType {
SmallInteger(u8),
Integer(i32),
Float(f64),
String(String),
Boolean(bool),
Tuple(BertTuple),
}
#[derive(Debug, PartialEq)]
pub struct BertTuple {
pub values: Vec<BertType>
}
pub struct Serializer;
pub trait Serialize<T> {
fn to_bert(&self, data: T) -> Vec<u8>;
}
impl Serializer {
fn enum_value_to_binary(&self, enum_value: BertType) -> Vec<u8> {
match enum_value {
BertType::SmallInteger(value_u8) => self.to_bert(value_u8),
BertType::Integer(value_i32) => self.to_bert(value_i32),
BertType::Float(value_f64) => self.to_bert(value_f64),
BertType::String(string) => self.to_bert(string),
BertType::Boolean(boolean) => self.to_bert(boolean),
BertType::Tuple(tuple) => self.to_bert(tuple),
}
}
}
// some functions for serialize bool/integer/etc. into Vec<u8>
// ...
impl Serialize<BertTuple> for Serializer {
fn to_bert(&self, data: BertTuple) -> Vec<u8> {
let mut binary: Vec<u8> = data.values
.iter()
.map(|&item| self.enum_value_to_binary(item)) // <-- what the issue there?
.collect();
let arity = data.values.len();
match arity {
0...255 => self.get_small_tuple(arity as u8, binary),
_ => self.get_large_tuple(arity as i32, binary),
}
}
}
But when compiling, I receive an error with iterating around map:
error: the trait bound `std::vec::Vec<u8>: std::iter::FromIterator<std::vec::Vec<u8>>` is not satisfied [E0277]
.collect();
^~~~~~~
help: run `rustc --explain E0277` to see a detailed explanation
note: a collection of type `std::vec::Vec<u8>` cannot be built from an iterator over elements of type `std::vec::Vec<u8>`
error: aborting due to previous error
error: Could not compile `bert-rs`.
How can I fix this issue with std::iter::FromIterator?
The problem is that enum_value_to_binary returns a Vec<u8> for each element in values. So you end up with an Iterator<Item=Vec<u8>> and you call collect::<Vec<u8>>() on that, but it doesn't know how to flatten the nested vectors. If you want all the values to be flattened into one Vec<u8>, then you should use flat_map instead of map:
let mut binary: Vec<u8> = data.values
.iter()
.flat_map(|item| self.enum_value_to_binary(item).into_iter())
.collect();
Or, slightly more idiomatic and performant, you can just have enum_value_to_binary return an iterator directly.
Also, the iter method returns an Iterator<Item=&'a T>, which means you are just borrowing the elements, but self.enum_value_to_binary wants to take ownership over the value. There's a couple of ways to fix that. One option would be to use into_iter instead of iter, which will give you the elements by value. If you do that, you'll move the arity variable up to before the binary variable, since creating the binary variable will take ownership (move) data.values.
The other option would be to change self.enum_value_to_binary to take it's argument by reference.
Also possible that you meant for the type of binary to actually be Vec<Vec<u8>>.

Unable to read file contents to string - Result does not implement any method in scope named `read_to_string`

I followed the code to open a file from Rust by Example:
use std::{env, fs::File, path::Path};
fn main() {
let args: Vec<_> = env::args().collect();
let pattern = &args[1];
if let Some(a) = env::args().nth(2) {
let path = Path::new(&a);
let mut file = File::open(&path);
let mut s = String::new();
file.read_to_string(&mut s);
println!("{:?}", s);
} else {
//do something
}
}
However, I got a message like this:
error[E0599]: no method named `read_to_string` found for type `std::result::Result<std::fs::File, std::io::Error>` in the current scope
--> src/main.rs:11:14
|
11 | file.read_to_string(&mut s);
| ^^^^^^^^^^^^^^ method not found in `std::result::Result<std::fs::File, std::io::Error>`
What am I doing wrong?
Let's look at your error message:
error[E0599]: no method named `read_to_string` found for type `std::result::Result<std::fs::File, std::io::Error>` in the current scope
--> src/main.rs:11:14
|
11 | file.read_to_string(&mut s);
| ^^^^^^^^^^^^^^ method not found in `std::result::Result<std::fs::File, std::io::Error>`
The error message is pretty much what it says on the tin - the type Result does not have the method read_to_string. That's actually a method on the trait Read.
You have a Result because File::open(&path) can fail. Failure is represented with the Result type. A Result may be either an Ok, which is the success case, or an Err, the failure case.
You need to handle the failure case somehow. The easiest is to just die on failure, using expect:
let mut file = File::open(&path).expect("Unable to open");
You'll also need to bring Read into scope to have access to read_to_string:
use std::io::Read;
I'd highly recommend reading through The Rust Programming Language and working the examples. The chapter Recoverable Errors with Result will be highly relevant. I think these docs are top-notch!
If your method returns Result<String, io::Error>, you can use ? on the functions that return a Result:
fn read_username_from_file() -> Result<String, io::Error> {
let mut f = File::open("hello.txt")?;
let mut s = String::new();
f.read_to_string(&mut s)?;
Ok(s)
}
If you cannot return a Result<String, io::Error> then you have to handle error case using expect as mentioned in the accepted answer or matching on the Result and panicking:
let file = File::open(&opt_raw.config);
let file = match file {
Ok(file) => file,
Err(error) => {
panic!("Problem opening the file: {:?}", error)
}
};
For more information, please refer to Recoverable Errors with Result.