What's the best way of handling errors in main method of actix-web? - rust

Is there a way to handle errors inside main method in actix-web?
I have the following code, as you can see there's three places in that function that can panic: the reference to the DATABASE_URL environment variable, the creation of the connection pool and the initialization of the template engine (tera).
#[actix_web::main]
async fn main() -> std::io::Result<()> {
dotenv().ok();
let database_url = std::env::var("DATABASE_URL").expect("DATABASE_URL must be set.");
let manager = ConnectionManager::<PgConnection>::new(database_url);
let pool = Pool::builder().build(manager).expect("Failed to create connection pool.");
HttpServer::new(move || {
let tera = Tera::new("templates/**/*").unwrap();
App::new()
.data(pool.clone())
.data(tera)
.route("/", web::get().to(index))
})
.bind("127.0.0.1:8000")?
.run()
.await
}
So, I tried to change the return type on that function and use a previously defined Enum in order to handle those errors:
async fn main() -> Result<HttpServer, ServerError> {
dotenv().ok();
let database_url = std::env::var("DATABASE_URL")?;
let manager = ConnectionManager::<PgConnection>::new(database_url);
let pool = Pool::builder().build(manager)?;
HttpServer::new(move || {
let tera = Tera::new("templates/**/*")?;
App::new()
.data(pool.clone())
.data(tera)
.route("/", web::get().to(index))
})
.bind("127.0.0.1:8000")?
.run()
.await
}
But the compiler shows this message:
[Running 'cargo run']
error[E0107]: missing generics for struct `HttpServer`
|
| async fn main() -> Result<HttpServer, ServerError> {
| ^^^^^^^^^^ expected 4 type arguments
|
note: struct defined here, with 4 type parameters: `F`, `I`, `S`, `B`
|
| pub struct HttpServer<F, I, S, B>
| ^^^^^^^^^^ - - - -
help: use angle brackets to add missing type arguments
|
| async fn main() -> Result<HttpServer<F, I, S, B>, ServerError> {
| ^^^^^^^^^^^^
error: aborting due to previous error
For more information about this error, try `rustc --explain E0107`.
error: could not compile
What's the best course of action that I can follow here? Should I handle this kind of errors within the main method?

Related

What signature can I use to download files using Axum and Tokio?

I'm using axum and this code (found here) to download files:
use axum::{
body::StreamBody,
http::{header, StatusCode},
response::{Headers, IntoResponse},
routing::get,
Router,
};
use std::net::SocketAddr;
use tokio_util::io::ReaderStream;
#[tokio::main]
async fn main() {
let app = Router::new().route("/", get(handler));
let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
axum::Server::bind(&addr)
.serve(app.into_make_service())
.await
.unwrap();
}
async fn handler() -> impl IntoResponse {
// `File` implements `AsyncRead`
let file = match tokio::fs::File::open("Cargo.toml").await {
Ok(file) => file,
Err(err) => return Err((StatusCode::NOT_FOUND, format!("File not found: {}", err))),
};
// convert the `AsyncRead` into a `Stream`
let stream = ReaderStream::new(file);
// convert the `Stream` into an `axum::body::HttpBody`
let body = StreamBody::new(stream);
let headers = Headers([
(header::CONTENT_TYPE, "text/toml; charset=utf-8"),
]);
Ok((headers, body))
}
Everything works. But I cannot find a way to move the below code in a separate function:
let file = match tokio::fs::File::open("Cargo.toml").await {
Ok(file) => file,
Err(err) => return Err((StatusCode::NOT_FOUND, format!("File not found: {}", err))),
};
I would like to use both tokio::fs::File and https://crates.io/crates/rust-s3 methods in this function.
So I need a "common type" which appear to be AsyncRead, I think.
What should be the signature of the function?
I tried with:
use tokio::io::AsyncRead;
pub struct Player {
db: Arc<DB>
}
impl Handler {
pub async fn player_pdf(
&self,
id: &str,
) -> Result<&(dyn AsyncRead)> {
//...use id here...
let file = &tokio::fs::File::open("player.pdf").await?;
Ok(file)
}
}
but I get the error:
error[E0308]: mismatched types
|
55 | Ok(file)
| -- ^^^^
| | |
| | expected reference, found struct `tokio::fs::File`
| | help: consider borrowing here: `&file`
| arguments to this enum variant are incorrect
|
= note: expected reference `&dyn tokio::io::AsyncRead`
found struct `tokio::fs::File`
I tried with: let file = &tokio::fs::File::open("player.pdf").await?; and I got:
error[E0515]: cannot return value referencing temporary value
|
43 | let file = &tokio::fs::File::open(...
| --------------------------- temporary value created here
...
55 | Ok(file)
| ^^^^^^^^ returns a value referencing data owned by the current function
What can I use?
Returning a generic "boxed" value might be the solution here:
impl Handler {
pub async fn player_pdf(
&self,
id: &str,
) -> Result<Box<dyn AsyncRead>> {
//...use id here...
Ok(Box::new(tokio::fs::File::open("player.pdf").await?))
}
}
Where now there's no dangling reference, it's encapsulated and fully owned.

Share state between actix-web server and async closure

I want to periodically fetch data (using asynchronous reqwest), which is then served at an http endpoint using actix-web as a server.
(I have a data source that has a fixed format, that I want to have read by a service that require a different format, so I need to transform the data.)
I've tried to combine actix concepts with the thread sharing state example from the Rust book, but I don't understand the error or how to solve it.
This is the code minified as much as I was able:
use actix_web::{get, http, web, App, HttpResponse, HttpServer, Responder};
use std::sync::{Arc, Mutex};
use tokio::time::{sleep, Duration};
struct AppState {
status: String,
}
#[get("/")]
async fn index(data: web::Data<Mutex<AppState>>) -> impl Responder {
let state = data.lock().unwrap();
HttpResponse::Ok()
.insert_header(http::header::ContentType::plaintext())
.body(state.status.to_owned())
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
let status_string = get_state().await.unwrap();
let app_data = Arc::new(Mutex::new(web::Data::new(AppState {
status: status_string,
})));
let app_data1 = Arc::clone(&app_data);
actix_web::rt::spawn(async move {
loop {
println!("I get executed every 2-ish seconds!");
sleep(Duration::from_millis(2000)).await;
let res = get_state().await;
let mut app_data = app_data1.lock().unwrap();
// Edit 2: this line is not accepted by the compiler
// Edit 2: *app_data.status = res.unwrap();
// Edit 2: but this line is accepted
*app_data = web::Data::new(AppState { status: res });
}
});
let app_data2 = Arc::clone(&app_data);
// Edit 2: but I get an error here now
HttpServer::new(move || App::new().app_data(app_data2).service(index))
.bind(("127.0.0.1", 9090))?
.run()
.await
}
async fn get_state() -> Result<String, Box<dyn std::error::Error>> {
let client = reqwest::Client::new().get("http://ipecho.net/plain".to_string());
let status = client.send().await?.text().await?;
println!("got status: {status}");
Ok(status)
}
But I get the following error:
error[E0308]: mismatched types
--> src/main.rs:33:32
|
33 | *app_data.status = res.unwrap();
| ---------------- ^^^^^^^^^^^^ expected `str`, found struct `String`
| |
| expected due to the type of this binding
error[E0277]: the size for values of type `str` cannot be known at compilation time
--> src/main.rs:33:13
|
33 | *app_data.status = res.unwrap();
| ^^^^^^^^^^^^^^^^ doesn't have a size known at compile-time
|
= help: the trait `std::marker::Sized` is not implemented for `str`
= note: the left-hand-side of an assignment must have a statically known size
Some errors have detailed explanations: E0277, E0308.
For more information about an error, try `rustc --explain E0277`.
Why do I suddenly get a str? Is there an easy fix or is my approach to solving this wrong?
Edit: Maybe removing the * is the right way to go, as Peter Hall suggests, but that gives me the following error instead:
error[E0594]: cannot assign to data in an `Arc`
--> src/main.rs:33:13
|
33 | app_data.status = res.unwrap();
| ^^^^^^^^^^^^^^^ cannot assign
|
= help: trait `DerefMut` is required to modify through a dereference, but it is not implemented for `Arc<AppState>`
error[E0507]: cannot move out of `app_data2`, a captured variable in an `Fn` closure
--> src/main.rs:38:49
|
37 | let app_data2 = Arc::clone(&app_data);
| --------- captured outer variable
38 | HttpServer::new(move || App::new().app_data(app_data2).service(index))
| ------- ^^^^^^^^^ move occurs because `app_data2` has type `Arc<std::sync::Mutex<Data<AppState>>>`, which does not implement the `Copy` trait
| |
| captured by this `Fn` closure
Some errors have detailed explanations: E0507, E0594.
For more information about an error, try `rustc --explain E0507`.
Edit 2: I now get the following error (code changes commented with 'Edit 2' above):
error[E0507]: cannot move out of `app_data2`, a captured variable in an `Fn` closure
--> src/main.rs:46:49
|
45 | let app_data2 = app_data.clone();
| --------- captured outer variable
46 | HttpServer::new(move || App::new().app_data(app_data2).service(index))
| ------- ^^^^^^^^^ move occurs because `app_data2` has type `Arc<Mutex<Data<AppState>>>`, which does not implement the `Copy` trait
| |
| captured by this `Fn` closure
For more information about this error, try `rustc --explain E0507`.
My Cargo.toml dependencies:
[dependencies]
actix-web = "4.2.1"
reqwest = "0.11.12"
tokio = "1.21.2"
async solution
I had my types mixed up a bit, having the app state as Arc<Mutex<T>> seemed to be the way to go, maybe it would be better with Arc<RwLock<T>>.
use actix_web::{get, http, web, App, HttpResponse, HttpServer, Responder};
use std::sync::{Arc, Mutex};
use tokio::time::{sleep, Duration};
struct AppState {
status: String,
}
#[get("/")]
async fn index(data: web::Data<Arc<Mutex<AppState>>>) -> impl Responder {
let state = data.lock().unwrap();
HttpResponse::Ok()
.insert_header(http::header::ContentType::plaintext())
.body(state.status.to_owned())
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
let status_string = get_state().await.unwrap();
let app_data = Arc::new(Mutex::new(AppState {
status: status_string,
}));
let app_data1 = app_data.clone();
actix_web::rt::spawn(async move {
loop {
println!("I get executed every 2-ish seconds!");
sleep(Duration::from_millis(2000)).await;
let res = get_state().await.unwrap();
let mut app_data = app_data1.lock().unwrap();
*app_data = AppState { status: res };
}
});
HttpServer::new(move || {
App::new()
.app_data(web::Data::new(app_data.clone()))
.service(index)
})
.bind(("127.0.0.1", 9090))?
.run()
.await
}
async fn get_state() -> Result<String, Box<dyn std::error::Error>> {
let client = reqwest::Client::new().get("http://ipecho.net/plain".to_string());
let status = client.send().await?.text().await?;
println!("got status: {status}");
Ok(status)
}
async/sync solution
Instead of doing the async get with reqwest I have a solution with the synchronous crate minreq (that I found after a lot of searching). I also chose to not use the #[actix_web::main] macro, and instead start the runtime explicitly at the end of my main function.
use actix_web::{get, http, rt, web, App, HttpResponse, HttpServer, Responder};
use std::sync::{Arc, Mutex};
use std::thread;
use std::time::Duration;
struct AppState {
status: String,
}
#[get("/")]
async fn index(data: web::Data<Arc<Mutex<AppState>>>) -> impl Responder {
let state = &data.lock().unwrap();
HttpResponse::Ok()
.insert_header(http::header::ContentType::plaintext())
.body(state.status.clone())
}
fn main() -> std::io::Result<()> {
let status_string = get_state().unwrap();
let app_data = Arc::new(Mutex::new(AppState {
status: status_string,
}));
let app_data1 = Arc::clone(&app_data);
thread::spawn(move || loop {
thread::sleep(Duration::from_millis(2000));
let res = get_state().unwrap();
let mut app_data = app_data1.lock().unwrap();
*app_data = AppState { status: res };
});
rt::System::new().block_on(
HttpServer::new(move || {
App::new()
.app_data(web::Data::new(app_data.clone()))
.service(index)
})
.bind(("127.0.0.1", 9090))?
.run(),
)
}
fn get_state() -> Result<String, Box<dyn std::error::Error>> {
let resp = minreq::get("http://ipecho.net/plain").send().unwrap();
let state = resp.as_str().unwrap();
Ok(state.to_string())
}

How can I get an Axum Handler function to return a Vec?

I'm learning how to use Axum with SQLx starting with this example. The basic example works, but I have problem trying to move forward. I am working with a simple database table as shown below:
todo | description
--------+--------------
todo_1 | doing todo 1
todo_2 | doing todo 2
todo_3 | doing todo 3
I am trying to simply get back "SELECT * FROM todos", but I am getting an error. I think I am getting the return of the Result type wrong but I am not sure what to do next. The entirety of main.rs is shown below.
//! Example of application using <https://github.com/launchbadge/sqlx>
//!
//! Run with
//!
//! ```not_rust
//! cd examples && cargo run -p example-sqlx-postgres
//! ```
//!
//! Test with curl:
//!
//! ```not_rust
//! curl 127.0.0.1:3000
//! curl -X POST 127.0.0.1:3000
//! ```
use axum::{
async_trait,
extract::{Extension, FromRequest, RequestParts},
http::StatusCode,
routing::get,
Router,
};
use sqlx::postgres::{PgPool, PgPoolOptions, PgRow};
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
use std::{net::SocketAddr, time::Duration};
#[tokio::main]
async fn main() {
tracing_subscriber::registry()
.with(tracing_subscriber::EnvFilter::new(
std::env::var("RUST_LOG").unwrap_or_else(|_| "example_tokio_postgres=debug".into()),
))
.with(tracing_subscriber::fmt::layer())
.init();
let db_connection_str = std::env::var("DATABASE_URL")
.unwrap_or_else(|_| "postgres://postgres:postgres#localhost".to_string());
// setup connection pool
let pool = PgPoolOptions::new()
.max_connections(5)
.connect_timeout(Duration::from_secs(3))
.connect(&db_connection_str)
.await
.expect("can connect to database");
// build our application with some routes
let app = Router::new()
.route(
"/",
get(using_connection_pool_extractor).post(using_connection_extractor),
)
.layer(Extension(pool));
// run it with hyper
let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
tracing::debug!("listening on {}", addr);
axum::Server::bind(&addr)
.serve(app.into_make_service())
.await
.unwrap();
}
// we can extract the connection pool with `Extension`
async fn using_connection_pool_extractor(
Extension(pool): Extension<PgPool>,
) -> Result<Vec<String>, (StatusCode, String)> {
sqlx::query_scalar("select * from todos")
.fetch_one(&pool)
.await
.map_err(internal_error)
}
// we can also write a custom extractor that grabs a connection from the pool
// which setup is appropriate depends on your application
struct DatabaseConnection(sqlx::pool::PoolConnection<sqlx::Postgres>);
#[async_trait]
impl<B> FromRequest<B> for DatabaseConnection
where
B: Send,
{
type Rejection = (StatusCode, String);
async fn from_request(req: &mut RequestParts<B>) -> Result<Self, Self::Rejection> {
let Extension(pool) = Extension::<PgPool>::from_request(req)
.await
.map_err(internal_error)?;
let conn = pool.acquire().await.map_err(internal_error)?;
Ok(Self(conn))
}
}
async fn using_connection_extractor(
DatabaseConnection(conn): DatabaseConnection,
) -> Result<String, (StatusCode, String)> {
let mut conn = conn;
sqlx::query_scalar("select 'hello world from pg'")
.fetch_one(&mut conn)
.await
.map_err(internal_error)
}
/// Utility function for mapping any error into a `500 Internal Server Error`
/// response.
fn internal_error<E>(err: E) -> (StatusCode, String)
where
E: std::error::Error,
{
(StatusCode::INTERNAL_SERVER_ERROR, err.to_string())
}
Compared to the example, I changed this function so that it returns a Vec<String> instead of a plain String, but I get a compiler error:
async fn using_connection_pool_extractor(
Extension(pool): Extension<PgPool>,
) -> Result<Vec<String>, (StatusCode, String)> {
sqlx::query_scalar("select * from todos")
.fetch_one(&pool)
.await
.map_err(internal_error)
}
error[E0277]: the trait bound `fn(Extension<Pool<sqlx::Postgres>>) -> impl Future<Output = Result<Vec<String>, (StatusCode, String)>> {using_connection_pool_extractor}: Handler<_, _>` is not satisfied
--> src/main.rs:52:17
|
52 | get(using_connection_pool_extractor).post(using_connection_extractor),
| --- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `Handler<_, _>` is not implemented for `fn(Extension<Pool<sqlx::Postgres>>) -> impl Future<Output = Result<Vec<String>, (StatusCode, String)>> {using_connection_pool_extractor}`
| |
| required by a bound introduced by this call
|
= help: the trait `Handler<T, ReqBody>` is implemented for `axum::handler::Layered<S, T>`
note: required by a bound in `axum::routing::get`
I am not sure what this error is suggesting or if it is even related to the actual problem.
Try using axum::Json:
async fn using_connection_pool_extractor(
Extension(pool): Extension<PgPool>,
) -> Result<axum::Json<Vec<String>>, (StatusCode, String)> {
sqlx::query_scalar("select * from todos")
.fetch_one(&pool)
.await
.map(|todos| axum::Json(todos))
.map_err(internal_error)
}
The reason why is that there's no implementation of the IntoResponse trait for Vec<T>. Here's a longer answer by Axum's author: https://users.rust-lang.org/t/axum-error-handling-trait-question/65530

Unable to specify lifetime parameter to solve compilation error

I'm learning Rust by doing small stuffs. I'm currently writing this app so, its first step is to read a config.json file, but I'm having this compilation error that I'm unable to resolve.
Here's my Cargo.toml dependencies
[dependencies]
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
regex = "1.4.3"
Here's the code
use std::error::Error;
use std::fs::File;
use std::io::BufReader;
use std::path::Path;
use regex::Regex;
use serde_json::Value;
fn _get_config() -> Box<dyn FnMut() -> &Value> {
let mut config = Box::new(Value::Null);
let a = || {
if *config == Value::Null {
match File::open("config.json").and_then(|file| -> Result<Value, std::io::Error> {
serde_json::from_reader(BufReader::new(file)).map_err(|e| e.into())
}) {
Ok(v) => *config = v,
Err(_) => {
*config = serde_json::from_str(
r#"
{
"DOMAIN_AS_ROOT_FOLDER": false,
"secret": "abcxyz"
}
"#,
)
.expect("Cannot initialize config, abort !");
}
}
}
config.as_ref()
};
Box::new(a)
}
fn main() {
let get_config = _get_config();
get_config();
}
And here's the compilation error
❯ cargo run
error[E0106]: missing lifetime specifier
--> src/main.rs:9:40
|
9 | fn _get_config() -> Box<dyn FnMut() -> &Value> {
| ^ expected named lifetime parameter
|
= help: this function's return type contains a borrowed value, but there is no value for it to be borrowed from
= note: for more information on higher-ranked polymorphism, visit https://doc.rust-lang.org/nomicon/hrtb.html
help: consider making the bound lifetime-generic with a new `'a` lifetime
|
9 | fn _get_config() -> Box<dyn for<'a> FnMut() -> &'a Value> {
| ^^^^^^^ ^^^
help: consider using the `'static` lifetime
|
9 | fn _get_config() -> Box<dyn FnMut() -> &'static Value> {
| ^^^^^^^^
error: aborting due to previous error
For more information about this error, try `rustc --explain E0106`.
error: could not compile `sieve_generator`
To learn more, run the command again with --verbose.
Basically the _get_config() returns a closure that allows me to get the config object everytime I call it. I don't see why there is such error since the variable config is supposed to live as long as my closure, there's nothing else as parameters here, why does it requires a lifetime here ? And how do I fix it ?
Thank you all very much for your time. I appreciate it a lot.
You'll never be able to do what you want as you can't guarantee to the compiler that the closure will NEVER go out of scope for the applications life time (which you'd have to do in this case because the compiler has no idea how long you'll hold on to the &Value reference).
Instead, since it looks like you're reading the configuration from disk only once why not store it in a static variable with the help of the lazy_static crate.
use std::fs::File;
use std::io::BufReader;
use serde_json::Value;
use lazy_static::lazy_static;
use std::sync::{Arc, Mutex};
lazy_static! {
static ref CONFIG: Arc<Mutex<Value>> = {
let file: std::io::Result<Value> = File::open( "config.json" ).and_then( | f | {
serde_json::from_reader( BufReader::new( f ) )
.map_err( | e | e.into( ) )
} );
Arc::new( Mutex::new( match file {
Ok( v ) => v,
_ => {
serde_json::from_str(
r#"
{
"DOMAIN_AS_ROOT_FOLDER": false,
"secret": "abcxyz"
}
"#
)
.expect( "Cannot initialize config, abort !" )
}
} ) )
};
}
fn main( ) {
let config = CONFIG.lock( ).unwrap( );
// Use your config here.
}
EDIT:
The Arc and Mutex are only necessary if you plan on mutating the Value. If you are not going to mutate the configuration at some point then you can ditch the use the of Arc and Mutex.
In both examples I removed the Option type as it wasn't needed.
lazy_static! {
static ref CONFIG: Value = {
let file: std::io::Result<Value> = File::open( "config.json" ).and_then( | f | {
serde_json::from_reader( BufReader::new( f ) )
.map_err( | e | e.into( ) )
} );
match file {
Ok( v ) => v,
_ => {
serde_json::from_str(
r#"
{
"DOMAIN_AS_ROOT_FOLDER": false,
"secret": "abcxyz"
}
"#
)
.expect( "Cannot initialize config, abort !" )
}
}
};
}

Cannot use Stream::take_while on an mpsc::channel: bool: Future is not satisfied [duplicate]

This question already has answers here:
How to use take_while with futures::Stream?
(2 answers)
Closed 3 years ago.
I want to run an event loop in one thread and handle data from a UDP socket until another thread signals to stop work.
This is a difficult task for me, so I want to start from a simpler task:
one thread starting the event loop and waiting for another thread to signal the end:
use futures::{future, future::Future, stream::Stream, sync::mpsc};
use std::{io, io::BufRead, thread};
fn main() {
let (mut tx, rx) = mpsc::channel::<bool>(1);
let thr = thread::spawn(|| {
let mut runtime = tokio::runtime::current_thread::Runtime::new().unwrap();
runtime.spawn(
future::lazy(|| {
println!("event loop started");
Ok(())
})
.and_then(rx.take_while(|x| *x == true).into_future()),
);
runtime.run()
});
let stdin = io::stdin();
for line in stdin.lock().lines() {
let line = line.unwrap();
println!("{}", line);
if line == "exit" {
tx.try_send(false).unwrap();
break;
}
}
thr.join().unwrap().unwrap();
}
This code doesn't compile:
error[E0277]: the trait bound `bool: futures::future::Future` is not satisfied
--> src/main.rs:14:26
|
14 | .and_then(rx.take_while(|x| *x == true).into_future()),
| ^^^^^^^^^^ the trait `futures::future::Future` is not implemented for `bool`
|
= note: required because of the requirements on the impl of `futures::future::IntoFuture` for `bool`
error[E0599]: no method named `into_future` found for type `futures::stream::take_while::TakeWhile<futures::sync::mpsc::Receiver<bool>, [closure#src/main.rs:14:37: 14:51], bool>` in the current scope
--> src/main.rs:14:53
|
14 | .and_then(rx.take_while(|x| *x == true).into_future()),
| ^^^^^^^^^^^
|
= note: the method `into_future` exists but the following trait bounds were not satisfied:
`futures::stream::take_while::TakeWhile<futures::sync::mpsc::Receiver<bool>, [closure#src/main.rs:14:37: 14:51], bool> : futures::stream::Stream`
`&mut futures::stream::take_while::TakeWhile<futures::sync::mpsc::Receiver<bool>, [closure#src/main.rs:14:37: 14:51], bool> : futures::stream::Stream`
How do I fix the compilation error?
Read and understand the documentation and function signature of methods you attempt to use:
fn take_while<P, R>(self, pred: P) -> TakeWhile<Self, P, R>
where
P: FnMut(&Self::Item) -> R,
R: IntoFuture<Item = bool, Error = Self::Error>,
Self: Sized,
take_while takes a closure that returns some type that must be convertible into a future; a bool is not convertible into a future. The simplest way to do this is via future::ok:
let thr = thread::spawn(|| {
let mut runtime = tokio::runtime::current_thread::Runtime::new().unwrap();
runtime.spawn({
rx.take_while(|&x| future::ok(x))
.for_each(|x| {
println!("{}", x);
future::ok(())
})
});
runtime.run()
});
See also:
The trait bound `(): futures::Future` is not satisfied when using TcpConnectionNew
But my problem also in joining future::lazy and rx.take_while
That's an unrelated problem to what you asked about. Again, we look at the docs, this time for Future::and_then:
fn and_then<F, B>(self, f: F) -> AndThen<Self, B, F>
where
F: FnOnce(Self::Item) -> B,
B: IntoFuture<Error = Self::Error>,
Self: Sized,
Similarly to take_while, it takes a closure and the closure must return something that can be convertible into a future. Your code doesn't provide a closure.
Then look at Stream::into_future. This returns a type that implements Future and returns a tuple. The first item in the tuple is a single value from the stream, the second is the stream itself, to allow getting more values.
To get all the item and error types correct, I've make liberal use of map(drop) and map_err(drop) — you will want to do something better for your data and error handling.
runtime.spawn({
future::lazy(|| {
println!("event loop started");
Ok(())
})
.and_then(|_| {
rx.take_while(|&x| future::ok(x))
.into_future()
.map(drop)
.map_err(drop)
})
.map(drop)
});
Really, you should just use a oneshot channel; it's much simpler:
use futures::{
future::{self, Future},
sync::oneshot,
};
use std::thread;
fn main() {
let (tx, rx) = oneshot::channel();
let thr = thread::spawn(|| {
let mut runtime = tokio::runtime::current_thread::Runtime::new().unwrap();
runtime.spawn({
future::lazy(|| {
println!("event loop started");
Ok(())
})
.and_then(|_| rx.map_err(drop))
});
runtime.run()
});
let lines = ["hello", "goodbye", "exit"];
for &line in &lines {
if line == "exit" {
tx.send(()).unwrap();
break;
}
}
thr.join().unwrap().unwrap();
}

Resources