Why does match will not release the mutable borrow until end of it's expression? - rust

I want to return client, In this function in any circumstances the code will not continue after match so rust should allow returning client.
pub async fn call_query2(mut client: Client<Compat<TcpStream>>, query:&str) -> Result<(Vec<tiberius::Row>,Client<Compat<TcpStream>>),(tiberius::error::Error,Client<Compat<TcpStream>>)> {
match client.query(query, &[]).await {
Ok(stream) =>{
match stream.into_first_result().await {
Ok(rows) => Ok((rows,client)),
Err(e) => Err((e,client))
}
},
Err(e) => Err((e,client))
}
}
but the compiler return this error message:
match client.query(query, &[]).await {
| ------------------------------
| |
| borrow of `client` occurs here
| a temporary with access to the borrow is created here ...
...
102 | Err(e) => Err((e,client))
| ^^^^^^ move out of `client` occurs here
103 | }
104 | }
| - ... and the borrow might be used here, when that temporary is dropped and runs the destructor for type `Result<QueryStream<'_>, tiberius::error::Error>`
It seems that match will not release the mutable borrow until end of it's expression, because this code will work but I'm not able to return client:
pub async fn call_query(mut client: Client<Compat<TcpStream>>, query:&str) -> Result<(Vec<tiberius::Row>,Client<Compat<TcpStream>>),tiberius::error::Error> {
let stream = client.query(query, &[]).await?;
return Ok((stream.into_first_result().await?, client));
}
Any idea?

The main thing is that the temporary value that is created with the match expression is not dropped (through the Drop trait) until after the match-expression. In a sense you can think of the code being something like this:
pub async fn call_query2(mut client: Client<Compat<TcpStream>>, query:&str) -> Result<(Vec<tiberius::Row>,Client<Compat<TcpStream>>), (tiberius::error::Error,Client<Compat<TcpStream>>)> {
let result;
let tmp = client.query(query, &[]).await;
match tmp {
Ok(stream) => {
match stream.into_first_result().await {
Ok(rows) => result = Ok((rows,client)),
Err(e) => result = Err((e,client))
}
},
Err(e) => result = Err((e,client))
}
// tmp.drop(); happens here implicitly
return result;
}
Note that the implicit call tmp.drop() here theoretically might need access to client from the borrow checker's perspective.
Your other example works because you're basically dropping result before the return statement. Conceptually something like this:
pub async fn call_query(mut client: Client<Compat<TcpStream>>, query:&str) -> Result<(Vec<tiberius::Row>,Client<Compat<TcpStream>>),tiberius::error::Error> {
let result = client.query(query, &[]).await;
if let Err(e) = result {
return Err( e );
}
let stream = result.unwrap();
return Ok((stream.into_first_result().await?, client));
}
Note that you couldn't return an Err( (e,client) ) inside the if here either or you'd get again the same error from the borrow checker, since result hasn't been dropped yet.
That being said -- why would you want to return client in the first place? Probably because you want to use client in the calling code again. But then your function shouldn't require the caller to give up ownership to client in the first place. Just change mut client: ... in the signature of your function to client: &mut ... and remove Client from the return value type like this:
pub async fn call_query2(client: &mut Client<Compat<TcpStream>>, query:&str) -> Result<Vec<tiberius::Row>, tiberius::error::Error> {
// same code as before, but changing the result so that
// it doesn't return client anymore, i.e,
// Ok( (rows,client) ) => Ok(rows) and same for Err
}
Now your calling code can still refer to client without needing it "passed back" from you function. I.e., you go from
if let Ok( (rows, old_new_client) ) = call_query(client, query) {
old_new_client.whatever();
}
to a much nicer
if let Ok(rows) = call_query2(&mut client, query) {
client.whatever();
}

Related

Assigning returned value in a match arm to a new variable

I am working on my first Rust project, a CLI application to find large files on a local filesystem. Rust has excellent documentation regarding match statements but I do not see how I can assign a value returned by the function passed to the match statement in a new variable:
pub fn run(config: Config) -> Result<(), Box<dyn Error>> {
let mut results_map: Option<Results>;
match search(&config.root_path) {
Err(e) => eprintln!("Error when calling run(): {:?}", e),
Ok(results) => results_map = results), // how to properly assign value returned by function here?
}
Ok(())
}
pub struct Results {
result_map: HashMap<String, u64>,
}
pub fn search(path: &str) -> Result<Results, io::Error> {
let root_path = Path::new(path);
let mut results = Results { result_map: HashMap::<String, u64>::new()};
match visit_dirs(root_path, &mut results) {
Err(e) => eprintln!("Error calling visit_dirs() from search(): {:?}", e),
_ => (),
}
Ok(results)
}
The functions you're calling in the match statements, search and visit_dirs return Result types. You have to use one of the arms of the match statement to cover the Ok (happy) case, next to where you're already covering the Err (error) case:
match search(&config.root_path) {
Ok(results) => return results,
Err(e) => eprintln!("Error when calling run(): {:?}", e),
}
Or you can assign the result of the match expression to a new variable:
let new_variable = match search(&config.root_path) {
Ok(results) => results, // no return here
Err(e) => {
eprintln!("Error when calling run(): {:?}", e)
// Either choose to panic (not recommended)
// panic!("Bad times lie ahead")
// Or propagate the error from your own Result
return Err(e);
},
}
You may want to look into the ? operator to simplify this in the common case.

Borrowed value does not live long enough when using async/await

I am trying to make two async api call and eventually get error E0597.
Here is a code:
async fn make_request() -> Result<()> {
.........
.........
.........
let mut result = client.get(uri).await?;
let some_key = result.headers().get("some_key");
let next_url = match some_key {
Some(url) => {
let some_result = client.get(Uri::from_static(url.to_str().unwrap())).await?
}
None => println!("....")
};
Ok(())
}
When I run this code the error "borrowed value does not live long enough argument requires that result is borrowed for `'static"
I have created a compile-able example based on your snipped to reproduce the error in the playground, and if you are able to do something like this in your question (for future reference), it usually helps you get more specific answers.
The Request passed into the function has no lifetime guarantees, so this will fail with the error you mentioned:
use http::{Request, Uri};
async fn make_request(result: &Request<()>) -> std::io::Result<()> {
match result.headers().get("some_key") {
// `url` is a reference to the string in the "some_key" header
Some(url) => {
let some_result = Uri::from_static(url.to_str().unwrap());
}
None => println!("....")
};
Ok(())
}
You can add that lifetime requirement, but that probably isn't what you need, and will likely give you the same error message, just in a different place:
async fn make_request_static(result: &'static Request<()>) -> std::io::Result<()> {
match result.headers().get("some_key") {
// because the request is static, so can be `url`
Some(url) => {
let some_result = Uri::from_static(url.to_str().unwrap());
}
None => println!("....")
};
Ok(())
}
Uri implements the FromStr trait, though, so you would be best off using that. There is no longer a lifetime requirement, so it can work with any string you pass in, even one which is currently borrowed:
// need to import the trait to use its methods
use std::str::FromStr;
async fn make_request_3(result: &Request<()>) -> std::io::Result<()> {
match result.headers().get("some_key") {
// because the request is static, so can be `url`
Some(url) => {
let some_result = Uri::from_str(url.to_str().unwrap());
}
None => println!("....")
};
Ok(())
}

How to create a Stream from reading and transforming a file?

I'm trying to read a file, decrypt it, and return the data. Because the file is potentially very big, I want to do this in a stream.
I cannot find a good pattern to implement the stream. I'm trying to do something like this:
let stream = stream::unfold(decrypted_init_length, |decrypted_length| async move {
if decrypted_length < start + length {
let mut encrypted_chunk = vec![0u8; encrypted_block_size];
match f.read(&mut encrypted_chunk[..]) {
Ok(size) => {
if size > 0 {
let decrypted = my_decrypt_fn(&encrypted_chunk[..]);
let updated_decrypted_length = decrypted_length + decrypted.len();
Some((decrypted, updated_decrypted_length))
} else {
None
}
}
Err(e) => {
println!("Error {}", e);
None
}
}
} else {
None
}
});
The problem is that f.read is not allowed in the above async closure with the following error:
89 | | match f.read(&mut encrypted_chunk[..]) {
| | -
| | |
| | move occurs because `f` has type `std::fs::File`, which does not implement the `Copy` trait
| | move occurs due to use in generator
I don't want to open f inside the closure itself. Is there any better way to fix this? I am OK with using a different crate or trait, or method (i.e. not stream::unfold).
I found a solution: using async-stream crate at here.
One of the reasons stream::unfold did not work for me is that the async move closure does not allow access mut variables outside, for example the f file handle.
Now with async-stream, I changed my code to the following, and it works: (note the yield added by this crate).
use async_stream::try_stream;
<snip>
try_stream! {
while decrypted_length < start + length {
match f.read(&mut encrypted_chunk[..]) {
Ok(size) =>
if size > 0 {
println!("read {} bytes", size);
let decrypted = my_decrypt_fn(&encrypted_chunk[..size], ..);
decrypted_length = decrypted_length + decrypted.len();
yield decrypted;
} else {
break
}
Err(e) => {
println!("Error {}", e);
break
}
}
}
}
UPDATE:
I found that async-stream has some limitations that I cannot ignore. I ended up implementing Stream directly and no longer using async-stream. Now my code looks like this:
pub struct DecryptFileStream {
f: File,
<other_fields>,
}
impl Stream for DecryptFileStream {
type Item = io::Result<Vec<u8>>;
fn poll_next(self: Pin<&mut Self>,
_cx: &mut Context<'_>) -> Poll<Option<io::Result<Vec<u8>>>> {
// read the file `f` of self and business_logic
//
if decrypted.len() > 0 {
Poll::Ready(Some(Ok(decrypted)))
} else {
Poll::Ready(None)
}
}
}
//. then use the above stream:
let stream = DecryptFileStream::new(...);
Response::new(Body::wrap_stream(stream))
stream::unfold is only for types that implement Stream, which in Rust is used exclusively for asynchronous programming. If you want to do synchronous reading, what you're calling a "stream" is tagged as implementing Read in Rust. Thus you can call Read::read() to read some data off the current position of your File (limited by the length of the buffer you pass in), and decrypt that data.

How to return an error from FuturesUnordered?

I have a set of futures to be run in parallel and if one fails I would like to get the error to return to the caller.
Here is what I have been testing so far:
use futures::prelude::*;
use futures::stream::futures_unordered::FuturesUnordered;
use futures::{future, Future};
fn main() {
let tasks: FuturesUnordered<_> = (1..10).map(|_| async_func(false)).collect();
let mut runtime = tokio::runtime::Runtime::new().expect("Unable to start runtime");
let res = runtime.block_on(tasks.into_future());
if let Err(_) = res {
println!("err");
}
}
fn async_func(success: bool) -> impl Future<Item = (), Error = String> {
if success {
future::ok(())
} else {
future::err("Error".to_string())
}
}
How can I get the error from any failed futures? Even better would be to stop running any pending futures if a single future fails.
Your code is already returning and handling the error. If you attempted to use the error, the compiler will quickly direct you to the solution:
if let Err(e) = res {
println!("err: {}", e);
}
error[E0277]: `(std::string::String, futures::stream::futures_unordered::FuturesUnordered<impl futures::future::Future>)` doesn't implement `std::fmt::Display`
--> src/main.rs:12:29
|
12 | println!("err: {}", e);
| ^ `(std::string::String, futures::stream::futures_unordered::FuturesUnordered<impl futures::future::Future>)` cannot be formatted with the default formatter
|
= help: the trait `std::fmt::Display` is not implemented for `(std::string::String, futures::stream::futures_unordered::FuturesUnordered<impl futures::future::Future>)`
= note: in format strings you may be able to use `{:?}` (or {:#?} for pretty-print) instead
= note: required by `std::fmt::Display::fmt`
The Err value is a tuple of your error and the original stream to continue pulling after you have dealt with the error. This is what Stream::into_future / StreamFuture does.
Access the first value in the tuple to get to the error:
if let Err((e, _)) = res {
println!("err: {}", e);
}
If you want to see all of the values, you could keep polling the stream over and over (but don't do this because it's probably inefficient):
let mut f = tasks.into_future();
loop {
match runtime.block_on(f) {
Ok((None, _)) => {
println!("Stream complete");
break;
}
Ok((Some(v), next)) => {
println!("Success: {:?}", v);
f = next.into_future();
}
Err((e, next)) => {
println!("Error: {:?}", e);
f = next.into_future();
}
}
}

Revert to previous value in a HashMap

I'm updating a key-value in a HashMap and then saving the HashMap to a file. I want to make sure that if saving to the file fails update is reverted. Here is the code I've written (Rust Playground):
use std::collections::HashMap;
use std::fs;
extern crate serde_json; // 1.0.37
fn save_map_to_file(map: &HashMap<String, String>) -> Result<(), ()> {
// serialize map to json
let map_as_string = match serde_json::to_string(map) {
Ok(json_map) => json_map,
Err(_) => return Err(()),
};
// write the json to a file
match fs::write("map.bin", map_as_string) {
Ok(_) => Ok(()),
Err(_) => Err(()),
}
}
fn change_map(map: &mut HashMap<String, String>) {
// save current value in "key1" (if exists)
let val = map.get("key1");
// insert a new value to "key1"
map.insert(String::from("key1"), String::from("value2"));
// try to save the map to a file
match save_map_to_file(map) {
Ok(_) => (),
Err(_) => {
// if save fails, revert back to the original value
match val {
Some(value) => {
// if "key1" existed before the change, revert back to
// original value
map.insert(String::from("key1"), value.to_string());
}
None => {
// if "key1" didn't exist before the change, remove the
// new "key1"->"value2" record
map.remove("key1");
}
}
()
}
}
}
fn main() {
let mut map: HashMap<String, String> = HashMap::new();
map.insert(String::from("key1"), String::from("value1"));
change_map(&mut map);
println!("Map: {:?}", map);
}
When I compile this code I get an error:
error[E0502]: cannot borrow `*map` as mutable because it is also borrowed as immutable
--> src/main.rs:24:5
|
21 | let val = map.get("key1");
| --- immutable borrow occurs here
...
24 | map.insert(String::from("key1"), String::from("value2"));
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ mutable borrow occurs here
...
31 | match val {
| --- immutable borrow later used here
I understand this error but I can't figure out what is the right way to revert the value if saving to file failed.
insert() returns the previous value, if any, so your code can be simplified a lot. This also solves your borrow problem:
fn change_map(map: &mut HashMap<&str, String>) {
let previous = map.insert("key1", String::from("value2"));
match save_map_to_file(map) {
Ok(_) => (),
Err(_) => {
previous
.and_then(|previous| map.insert("key1", previous))
.or_else(|| map.remove("key1"));
}
}
}
Rust compiler is not happy with this borrow, just get rid of it
--- let val = map.get("key1");
+++ let val = map.get("key1").cloned();

Resources