projectfluent / fluent-rs

Rust implementation of Project Fluent
https://projectfluent.org
Apache License 2.0
1.05k stars 95 forks source link

Error: (dyn std::any::Any + 'static) cannot be sent between threads safely #167

Closed sbrl closed 4 years ago

sbrl commented 4 years ago

Hello,

I'm a relatively new Rust programmer, and I'm implementing a web app with actix - which spawns multiple worker threads. As part of this, I have a GlobalState struct that is shared, and is available to all request handling methods. In here I have things such as a Database connection (with r2d2_sqlite) for example.

Since I'm at the beginning of building my web app, I thought it would be great to build in multi-language support. Having built web apps previously without considering this at the get-go, I've been bitten by this before.

To this end, I thought I would try using Fluent. I think I heard about it at first through a Mozilla Hacks blog post IIRC.

To do this, from the crate documentation I understand I need to parse the fluent source files and package them up into a FluentBundle<FluentResource> at run time. To hold this, I have the following struct:

#[derive(Clone)]
pub struct Translations {
    langs: HashMap<LanguageIdentifier, FluentBundle<FluentResource>>
}

It is my understanding here that I need to derive from Clone to avoid (unhelpful) errors such as (dyn std::any::Any + 'static) cannot be sent between threads safely. This has been the case already in other places in my codebase.

Unfortunately, I've run into a bit of a problem. As soon as I added the HashMap<LanguageIdentifier, FluentBundle<FluentResource>>, I started getting the following error:

error[E0277]: `(dyn std::any::Any + 'static)` cannot be sent between threads safely
  --> wopplebloxd/src/http_server/mod.rs:27:5
   |
27 |     #[actix_rt::main]
   |     ^^^^^^^^^^^^^^^^^ `(dyn std::any::Any + 'static)` cannot be sent between threads safely
   |

(full error output: https://hastebin.com/alugesigub)

From this error, it is my understanding that FluentBundle is not currently thread safe.

As a relatively new Rust programmer (I have experience with other languages though), I'm unsure as to what I should do.

savannidgerinel commented 4 years ago

This has already been resolved: Introduce a concurrent version of FluentBundle #163

I was running into it, too, which is why I found this thread. After investigating a bit deeper, I found that all we need to do is to instantiate fluent::concurrent::FluentBundle instead of fluent::FluentBundle.

zbraniecki commented 4 years ago

Yes! We quite recently added fluent::concurrent::FluentBundle for multithreaded use cases! Hope that helps!

@sbrl - let me know if your issue is addressed!

sbrl commented 4 years ago

Hey, thanks for the reply @zbraniecki, @savannidgerinel! I didn't notice that because it's not actually documented in the documentation. The link there to FluentBundle sends me here, but I see a 404:

image

Using that does fix some issues, yeah! Now I get this error:

error[E0277]: the trait bound `intl::Translations: std::clone::Clone` is not satisfied
 --> wopplebloxd/src/global_state.rs:9:5
  |
9 |     pub translations : Translations
  |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `std::clone::Clone` is not implemented for `intl::Translations`
  |
  = note: required by `std::clone::Clone::clone`

This is because I'm doing this:

#[derive(Clone)]
pub struct GlobalState {
    pub sitename : String,
    pub db : Database,
    pub translations : Translations
}

This is my global state object for actix:

pub async fn start(&self, port: i16, global_state : GlobalState) -> std::io::Result<()> {
    let address = format!("127.0.0.1:{}", port);

    handlers::print_embedded_files();

    info!("Starting listener on http://{}", address);

    HttpServer::new(move || {
        App::new()
            .data(global_state.clone())
            .wrap(Logger::default())
            .route("/static/{filepath:.*}", web::get().to(handlers::handle_static))
            .route("/", web::get().to(index))
    })
        .keep_alive(120) // TODO: Read this from a config file here
        .bind(address)? //.expect("Error: Failed to bind to address (is another processs using it?)")
        .run()
        .await
}

I'm guessing that using #[derive(Clone)] here is not really the correct solution here for a global (immutable in theory in request handlers I'm pretty sure) state object?

I'm finding Rust very different to what I'm used to, so a pointer as to the correct way to implement this would be most appreciated :slightly_smiling_face:

I guess we can call this issue solved - though I still have some issues to work out :P

zbraniecki commented 4 years ago

haha, yeah, it takes a bit to learn your way through it! As a word of encouragement - I believe it also teaches you to be a better engineer :)