You have already discovered that tokio::spawn is the correct tool for this task, since you essentially want to use some async code in a "fire-and-forget" way, without waiting for it in the main program flow. You just need a little adjustments to your code.
First, for the error you're quoting - it is due to the fact that you have to somehow handle the possible error during the request. You can simply add question marks after each await returning Result, but then you run into the following:
error[E0277]: the `?` operator can only be used in an async block that returns `Result` or `Option` (or another type that implements `Try`)
--> src/main.rs:11:23
|
9 | tokio::spawn(async move {
| _________________________________-
10 | | let url = format!("https://api[insertsite].com/blahblah&apiKey={}", i);
11 | | let res = client.get(url).send().await?.text().await?;
| | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ cannot use the `?` operator in an async block that returns `()`
12 | | println!("{:?}", res);
13 | | });
| |_________- this function should return `Result` or `Option` to accept `?`
|
= help: the trait `Try` is not implemented for `()`
= note: required by `from_error`
The "this function" bit might be a little misleading, since the rest of the error is talking about "async block", but the gist is simple: if you use question mark to bubble error out of somewhere, the happy path must return Result, too. Well, let's simply add Ok(()) to the end of async block, and here's the next error:
error[E0282]: type annotations needed
--> src/main.rs:11:51
|
11 | let res = client.get(url).send().await?.text().await?;
| ^ cannot infer type of error for `?` operator
|
= note: `?` implicitly converts the error value into a type implementing `From<reqwest::Error>`
That's expected, too - in the ordinary function return type would be provided by its signature, but in the async block that's not an option. We can, however, use the turbofish:
tokio::spawn(async move {
// snip
Ok::<_, Box<dyn std::error::Error + Send + Sync>>(())
});
Note that we need both Send and Sync: the former is necessary for the resulting error to be able to cross the async block boundary, and the latter - because Box<dyn Error + Send> can't be created with From/Into conversion from other errors.
Now, we're left with two more compile errors, independent of the ones before:
error[E0597]: `keys` does not live long enough
--> src/main.rs:8:14
|
8 | for i in keys.iter() {
| ^^^^
| |
| borrowed value does not live long enough
| cast requires that `keys` is borrowed for `'static`
...
18 | }
| - `keys` dropped here while still borrowed
error[E0382]: use of moved value: `client`
--> src/main.rs:9:33
|
7 | let client = reqwest::Client::builder().build()?;
| ------ move occurs because `client` has type `reqwest::Client`, which does not implement the `Copy` trait
8 | for i in keys.iter() {
9 | tokio::spawn(async move {
| _________________________________^
10 | | let url = format!("https://api[insertsite].com/blahblah&apiKey={}", i);
11 | | let res = client.get(url).send().await?.text().await?;
| | ------ use occurs due to use in generator
12 | | println!("{:?}", res);
13 | | Ok::<_, Box<dyn std::error::Error + Send + Sync>>(())
14 | | });
| |_________^ value moved here, in previous iteration of loop
The first of them can be fixed in three possible ways:
- use
Vec insted of array and drop the .iter(),
- use
std::array::IntoIter,
- use
.iter().copied().
In each case, we'll get &'static str, which can be passed into the async block.
The second is even easier: we can simply do let client = client.clone(); at the start of each iteration, before tokio::spawn. This is cheap, since Client uses Arc internally.
Here's the playground, with all the changes described above.
Finally, here's the version which I'd personally recommend to base your code on, since it not only compiles, but additionally does not swallow the errors which can arise on request:
use core::time::Duration;
use tokio::time::sleep;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let keys = ["key", "key", "key"];
let client = reqwest::Client::builder().build()?;
for i in std::array::IntoIter::new(keys) {
let client = client.clone();
tokio::spawn(async move {
let url = format!("https://api[insertsite].com/blahblah&apiKey={}", i);
// moved actual request into inner block...
match async move {
let res = client.get(url).send().await?.text().await?;
// ...returning Result with explicit error type,
// so that the wholeinner async block is treated as "try"-block...
Ok::<_, Box<dyn std::error::Error + Send + Sync>>(res)
}
.await
{
// ...and matching on the result, to have either text or error
// (here it'll always be error, due to invalid URL)
Ok(res) => println!("{:?}", res),
Err(er) => println!("{}", er),
}
});
sleep(Duration::from_millis(97)).await;
}
// just to wait for responses
sleep(Duration::from_millis(1000)).await;
Ok(())
}
Playground