Simplifying Rust matching with combinators - error-handling

I have something like this:
match fnX(
fnY(x), // Returns Result<(), X>
) // Returns Result<(), Y>
.await
{
Ok(v) => {
if v.is_err() {
error!("error = {}", v);
}
}
Err(e) => error!("error = {}", e),
};
How can I write this with combinators so that I only have to error! once? I don't want to do anything with the Ok value, just print the error whether it comes from fnX or fnY.

I'm assuming that you meant to simplify something like this (removing the .await that is unrelated to the issue):
match fnX(x) { // Returns Result<X, EX>
Ok(y) => match fnY(y) { // Returns Result<Y, EY>
Ok(_) => println!("Success!"),
Err(e) => error!("error = {}", e),
},
Err(e) => error!("error = {}", e),
}
If the error types are the same, you can simplify the code with and_then:
match fnX(x).and_then(fnY) {
Ok(_) => println!("Success!"),
Err(e) => error!("error = {}", e),
}
If the error types are different, you can use map_err to convert them to a single type:
match fnX(x)
.map_err(MyError::from)
.and_then(|y| fnY(y).map_err(MyError::from))
{
Ok(_) => println!("Success!"),
Err(e) => error!("error = {}", e),
}
The latter can be simplified using the latest development version of the map_for crate:
match map_for!(y <- fnX (x);
v <- fnY (y);
=> v)
{
Ok(_) => println!("Success"),
Err(e # MyError { .. }) => error!("error = {}", e),
}
Note that the # MyError {..} annotation is only required if the compiler is unable to infer the error type automatically.
Full disclaimer: I am the author of the map_for crate.

You don't need such a "combinator".
fnX accepts an argument of type Result<(), X> and returns a Result<(), Y>
When the code is convoluted it may help to separate the expressions, making it more readable.
let result = fnY(x);
match fnX(result).await {
Ok(v) => {
// here v is ok value, in this case ()
}
Err(e) => error!("error = {}", e),
};

Related

Unable to establish a TLS connection using Rust, Mio and TlsConnector

I'm trying to create a websocket client using tokio_tungstenite and mio but I couldn't initialize a stream because of handshake issues. Here is the code I have:
let addr: Vec<_> = ws_url
.to_socket_addrs()
.map_err(|err| ClientError {
message: err.to_string(),
})
.unwrap()
.collect();
println!("{:?}", addr);
let connector = TlsConnector::new().unwrap();
let stream = TcpStream::connect(addr[0]).unwrap();
let mut stream = match connector.connect(ws_url.as_str(), stream) {
Ok(stream) => Ok(stream),
Err(err) => match err {
native_tls::HandshakeError::Failure(err) => Err(ClientError::new(format!(
"Handshake failed: {}",
err.to_string()
))),
native_tls::HandshakeError::WouldBlock(mh) => match mh.handshake() {
Ok(stream) => Ok(stream),
Err(err) => Err(ClientError::new(format!( // <-- the handshake process was interrupted
"Handshake failed: {}",
err.to_string()
))),
},
},
}?;
This code fails on mh.handshake() with an error: the handshake process was interrupted.
Does anyone know why that happens and how to fix it?
After long research decided to not use mio completely and create an event loop manually. This is doable but too time consuming for a simple task I do.
If you were to create a single threaded event loop, you can just use tungstenite and set_nonblocking method of the underling socket:
let url = Url::parse(ws_url.as_str()).unwrap();
match tungstenite::connect(url) {
Ok((mut sock, _)) => {
let s = sock.get_mut();
match s {
tungstenite::stream::MaybeTlsStream::Plain(s) => s.set_nonblocking(true),
tungstenite::stream::MaybeTlsStream::NativeTls(s) => {
s.get_mut().set_nonblocking(true)
}
x => panic!("Received unknown stream type: {:?}", x),
}
.map_err(|err| ClientError::new(format!("Failed to unblock the socket: {}", err)))
.unwrap();
Ok(Box::new(KrakenWsClient { sock }))
}
Err(err) => Err(ClientError {
message: format!(
"Failed to establish websocket connection: {}",
err.to_string()
),
}),
}
Then reading will look like this:
fn next_message(&mut self) -> Result<Option<Message>> {
match self.sock.read_message() {
Ok(msg) => self.parse_msg(msg),
Err(err) => match err {
Error::Io(err) => {
if err.kind() == ErrorKind::WouldBlock {
Ok(None)
} else {
Err(ClientError::new(format!(
"Error reading from websocket: {}",
err
)))
}
}
_ => Err(ClientError::new(format!(
"Error reading from websocket: {}",
err
))),
},
}
}
Remember to control timing of yours event loop to prevent it using 100% of CPU. Hope this helps!

Is there a way to get an OS error code from a std::io::Error?

When I run the following:
use std::fs::File;
fn main() {
let filename = "not_exists.txt";
let reader = File::open(filename);
match reader {
Ok(_) => println!(" * file '{}' opened successfully.", filename),
Err(e) => {
println!("{:?}", &e);
}
}
}
The output is:
Os { code: 2, kind: NotFound, message: "No such file or directory" }
Is it possible to get that code as an integer?
Use io::Error::raw_os_error:
match reader {
Ok(_) => println!(" * file '{}' opened successfully.", filename),
Err(e) => println!("{:?}", e.raw_os_error()),
}
Output:
Some(2)
See also:
How does one get the error message as provided by the system without the "os error n" suffix?
Yes, use the raw_os_error method on std::io::Error. Example:
use std::fs::File;
fn main() {
let filename = "not_exists.txt";
let reader = File::open(filename);
match reader {
Ok(_) => println!(" * file '{}' opened successfully.", filename),
Err(e) => {
println!("{:?} {:?}", e, e.raw_os_error());
}
}
}
playground

How do I iterate over a Vec of functions returning Futures in Rust?

Is it possible to loop over a Vec, calling a method that returns a Future on each, and build a chain of Futures, to be evaluated (eventually) by the consumer? Whether to execute the later Futures would depend on the outcome of the earlier Futures in the Vec.
To clarify:
I'm working on an application that can fetch data from an arbitrary set of upstream sources.
Requesting data would check with each of the sources, in turn. If the first source had an error (Err), or did not have the data available (None), then the second source would be tried, and so on.
Each source should be tried exactly once, and no source should be tried until all of the sources before have returned their results. Errors are logged, but otherwise ignored, passing the query to the next upstream data source.
I have some working code that does this for fetching metadata:
/// Attempts to read/write data to various external sources. These are
/// nested types, because a data source may exist as both a reader and a writer
struct StoreManager {
/// Upstream data sources
readers: Vec<Rc<RefCell<StoreRead>>>,
/// Downstream data sinks
writers: Vec<Rc<RefCell<StoreWrite>>>,
}
impl StoreRead for StoreManager {
fn metadata(self: &Self, id: &Identifier) -> Box<Future<Option<Metadata>, Error>> {
Box::new(ok(self.readers
.iter()
.map(|store| {
executor::block_on(store.borrow().metadata(id)).unwrap_or_else(|err| {
error!("Error on metadata(): {:?}", err);
None
})
})
.find(Option::is_some)
.unwrap_or(None)))
}
}
Aside from my unhappiness with all of the Box and Rc/RefCell nonsense, my real concern is with the executor::block_on() call. It blocks, waiting for each Future to return a result, before continuing to the next.
Given that it's possible to call fn_returning_future().or_else(|_| other_fn()) and so on, is it possible to build up a dynamic chain like this? Or is it a requirement to fully evaluate each Future in the iterator before moving to the next?
You can use stream::unfold to convert a single value into a stream. In this case, we can use the IntoIter iterator as that single value.
use futures::{executor, stream, Stream, TryStreamExt}; // 0.3.4
type Error = Box<dyn std::error::Error>;
type Result<T, E = Error> = std::result::Result<T, E>;
async fn network_request(val: i32) -> Result<i32> {
// Just for demonstration, don't do this in a real program
use std::{
thread,
time::{Duration, Instant},
};
thread::sleep(Duration::from_secs(1));
println!("Resolving {} at {:?}", val, Instant::now());
Ok(val * 100)
}
fn requests_in_sequence(vals: Vec<i32>) -> impl Stream<Item = Result<i32>> {
stream::unfold(vals.into_iter(), |mut vals| async {
let val = vals.next()?;
let response = network_request(val).await;
Some((response, vals))
})
}
fn main() {
let s = requests_in_sequence(vec![1, 2, 3]);
executor::block_on(async {
s.try_for_each(|v| async move {
println!("-> {}", v);
Ok(())
})
.await
.expect("An error occurred");
});
}
Resolving 1 at Instant { tv_sec: 6223328, tv_nsec: 294631597 }
-> 100
Resolving 2 at Instant { tv_sec: 6223329, tv_nsec: 310839993 }
-> 200
Resolving 3 at Instant { tv_sec: 6223330, tv_nsec: 311005834 }
-> 300
To ignore Err and None, you have to shuttle the Error over to the Item, making the Item type a Result<Option<T>, Error>:
use futures::{executor, stream, Stream, StreamExt}; // 0.3.4
type Error = Box<dyn std::error::Error>;
type Result<T, E = Error> = std::result::Result<T, E>;
async fn network_request(val: i32) -> Result<Option<i32>> {
// Just for demonstration, don't do this in a real program
use std::{
thread,
time::{Duration, Instant},
};
thread::sleep(Duration::from_secs(1));
println!("Resolving {} at {:?}", val, Instant::now());
match val {
1 => Err("boom".into()), // An error
2 => Ok(None), // No data
_ => Ok(Some(val * 100)), // Success
}
}
fn requests_in_sequence(vals: Vec<i32>) -> impl Stream<Item = Result<Option<i32>>> {
stream::unfold(vals.into_iter(), |mut vals| async {
let val = vals.next()?;
let response = network_request(val).await;
Some((response, vals))
})
}
fn main() {
executor::block_on(async {
let s = requests_in_sequence(vec![1, 2, 3]);
let s = s.filter_map(|v| async move { v.ok() });
let s = s.filter_map(|v| async move { v });
let mut s = s.boxed_local();
match s.next().await {
Some(v) => println!("First success: {}", v),
None => println!("No successful requests"),
}
});
}
Resolving 1 at Instant { tv_sec: 6224229, tv_nsec: 727216392 }
Resolving 2 at Instant { tv_sec: 6224230, tv_nsec: 727404752 }
Resolving 3 at Instant { tv_sec: 6224231, tv_nsec: 727593740 }
First success: 300
is it possible to build up a dynamic chain like this
Yes, by leveraging async functions:
use futures::executor; // 0.3.4
type Error = Box<dyn std::error::Error>;
type Result<T, E = Error> = std::result::Result<T, E>;
async fn network_request(val: i32) -> Result<Option<i32>> {
// Just for demonstration, don't do this in a real program
use std::{
thread,
time::{Duration, Instant},
};
thread::sleep(Duration::from_secs(1));
println!("Resolving {} at {:?}", val, Instant::now());
match val {
1 => Err("boom".into()), // An error
2 => Ok(None), // No data
_ => Ok(Some(val * 100)), // Success
}
}
async fn requests_in_sequence(vals: Vec<i32>) -> Result<i32> {
let mut vals = vals.into_iter().peekable();
while let Some(v) = vals.next() {
match network_request(v).await {
Ok(Some(v)) => return Ok(v),
Err(e) if vals.peek().is_none() => return Err(e),
Ok(None) | Err(_) => { /* Do nothing and try the next source */ }
}
}
Err("Ran out of sources".into())
}
fn main() {
executor::block_on(async {
match requests_in_sequence(vec![1, 2, 3]).await {
Ok(v) => println!("First success: {}", v),
Err(e) => println!("No successful requests: {}", e),
}
});
}
See also:
Creating Diesel.rs queries with a dynamic number of .and()'s
is it a requirement to fully evaluate each Future in the iterator before moving to the next
Isn't that part of your own requirements? Emphasis mine:
Requesting data would check with each of the sources, in turn. If the first source had an error (Err), or did not have the data available (None), then the second source would be tried

Can Rust consume an iterator passed into a function?

I'm trying to implement a simple REPL calculator in Rust and I'm hitting brick walls all over the place.
I'm consuming chars while iterating over a hardcoded string. When I hit a numeric character I want to pass control over to a function that will consume the rest of the number (assuming the number has more than one digit) and return the number, converted to an Integer.
I'm having trouble with passing Chars iterator to a function. The error I'm getting is use of moved value: 'iter'.
I understand that I can't mutate something that I gave to someone else - something that had its ownership moved - but I don't know any other way of doing this, especially since the Chars iterator is non-copyable.
#[derive(Clone, Debug)]
enum Token {
Addition,
Substraction,
Multiplication,
Division,
Integer(i32),
Error,
}
fn consume_number(mut iter: std::str::Chars) -> Option<i32> {
while let Some(item) = iter.next() {
println!("{:?}", item);
}
return Some(1337);
}
fn tokenize(line: &str) -> Vec<Token> {
let mut iter = line.chars();
let mut tokens = Vec::new();
let mut token;
while let Some(c) = iter.next() {
if c.is_whitespace() { continue };
if c.is_digit(10) {
token = match consume_number(iter) {
Some(i32) => Token::Integer(i32),
None => Token::Error,
};
} else {
token = match c {
'+' => Token::Addition,
'-' => Token::Substraction,
'*' => Token::Multiplication,
'/' => Token::Division,
_ => Token::Error,
};
};
tokens.push(token);
}
return tokens;
}
fn main() {
let line = "631 * 32 + 212 - 15 / 89";
println!("{:?}", tokenize(&line));
}
The answer is yes, it's done in the FromIterator trait.
What you experience here is much more basic:
fn consume_number(mut iter: std::str::Chars) -> Option<i32> { ... }
while let Some(c) = iter.next() {
...
match_consume_number(iter)
...
}
When calling match_consume_number you are transferring ownership of the iterator to it. It means that at the next iteration of the loop body, this iter variable is no longer available.
If the iterator is meant to still be usable afterward, you should pass a reference to it:
fn consume_number(iter: &mut std::str::Chars) -> Option<i32> { ... }
while let Some(c) = iter.next() {
...
match_consume_number(&mut iter)
...
}
You were close!

What is the best variant for appending a new line in a text file?

I am using this code to append a new line to the end of a file:
let text = "New line".to_string();
let mut option = OpenOptions::new();
option.read(true);
option.write(true);
option.create(true);
match option.open("foo.txt") {
Err(e) => {
println!("Error");
}
Ok(mut f) => {
println!("File opened");
let size = f.seek(SeekFrom::End(0)).unwrap();
let n_text = match size {
0 => text.clone(),
_ => format!("\n{}", text),
};
match f.write_all(n_text.as_bytes()) {
Err(e) => {
println!("Write error");
}
Ok(_) => {
println!("Write success");
}
}
f.sync_all();
}
}
It works, but I think it's too difficult. I found option.append(true);, but if I use it instead of option.write(true); I get "Write error".
Using OpenOptions::append is the clearest way to append to a file:
use std::fs::OpenOptions;
use std::io::prelude::*;
fn main() {
let mut file = OpenOptions::new()
.write(true)
.append(true)
.open("my-file")
.unwrap();
if let Err(e) = writeln!(file, "A new line!") {
eprintln!("Couldn't write to file: {}", e);
}
}
As of Rust 1.8.0 (commit) and RFC 1252, append(true) implies write(true). This should not be a problem anymore.
Before Rust 1.8.0, you must use both write and append — the first one allows you to write bytes into a file, the second specifies where the bytes will be written.