projectfluent / fluent-rs

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

error[E0277]: `(dyn Any + 'static)` cannot be sent between threads safely #299

Open lynn2910 opened 1 year ago

lynn2910 commented 1 year ago

Hi, I know this error is an issue that has been closed, but I don't understand HOW to access the "new_concurrent" method.

When I use this code:

  {
    let ftl_string = include_str!("../assets/languages/fr.ftl");
    let res = FluentResource::try_new(ftl_string.to_string()).expect("Failed to parse an FTL string.");

    let lang = langid!("fr-FR");
    let mut bundle = FluentBundle::new(vec![lang.clone()]);

    bundle.add_resource(res).expect("Failed to add FTL resources to the bundle.");

    langs.insert(lang, bundle);
  }

It doesn't give any error (in that part, I use threads so this part broke my whole project). But, when I try to use the FluentBundle::new_concurrent method instead of FluentBundle::new, it says that this function doesn't exist, and I can't find anything about it in the documentation.

How can we access to this method ?

zbraniecki commented 1 year ago

Ah, good call. It's not very ergonomic now, you need to do:

let bundle = bundle::FluentBundle<_, intl_memoizer::concurrent::IntlMemoizer>;
lynn2910 commented 1 year ago

Thank you, it works perfectly now !

lynn2910 commented 1 year ago

For those who were also wondering how to do it:

// an example of a basic fluent implementation with a HashMap to store languages (works with threads!)
// Don't forget to add the necessary dependencies to your Cargo.toml
use std::collections::HashMap;
use fluent::{ FluentResource, bundle::FluentBundle };
use unic_langid::{self, LanguageIdentifier, langid};
use intl_memoizer;

// Very important !!!!
type TranslationType = FluentBundle<FluentResource, intl_memoizer::concurrent::IntlLangMemoizer>;

fn load() -> HashMap<LanguageIdentifier, TranslationType> {
  let mut langs: HashMap<LanguageIdentifier, TranslationType> = HashMap::new();
  {
    let ftl_string = include_str!("../assets/languages/fr.ftl"); // you can use a &str or read a file instead of include_str !
    let res = FluentResource::try_new(ftl_string.to_string()).expect("Failed to parse an FTL string.");

    let lang = langid!("fr-FR");
    let mut bundle = FluentBundle::new_concurrent(vec![lang.clone()]);

    bundle.add_resource(res).expect("Failed to add FTL resources to the bundle.");

    langs.insert(lang, bundle);
  }
  langs
}

It would be nice if we could find a better documentation on how to do this 😀

zbraniecki commented 1 year ago

Would appreciate PR! Maybe even add type alias in bundle::concurrent::FluentBundle? :)

ClementGre commented 1 year ago

Is there a way to achieve this while using the fluent-fallback crate ?

When using this crate, a Bundles type is created using the Localization::with_env(...).bundles() function :

let bundles = Localization::with_env(
        vec!["back.ftl".into(), "common.ftl".into()],
        true,
        vec![langid!("fr-FR"), langid!("en-US")],
        ResourceManager::new("../translations/{locale}/{res_id}".to_string()),
    ).bundles();

Then, I get the trait `Send` is not implemented for `(dyn Any + 'static) when using bundle as a parameter T where T: Send + Sync + 'static.

zbraniecki commented 1 year ago

You would want to introduce a fluent_fallback::concurrent::Localization likely. We didn't get to write a PR for it yet.

ClementGre commented 1 year ago

Well, I understand why to use intl_memoizer::concurrent::IntlLangMemoizer for FluentBundle, but there is no such type parameter in Localization.

Then I don't know at all how could I introduce a fluent_fallback::concurrent::Localization. (Moreover, it is not the Localization impl that has threading issues, but the Bundles impl produced by .bundles).

(Sorry, I'm pretty new to Rust)

zbraniecki commented 1 year ago

Then I don't know at all how could I introduce a fluent_fallback::concurrent::Localization.

You would need to create an equivalent of Localization in concurrent module that is parametrized on concurrent::FluentBundle.

You would need to write it all, there is no such solution yet.

ClementGre commented 1 year ago

Why would it be parameterized on FluentBundle ? Localization does not uses FluentBundle at all and it generates a Bundles object that represents all of the loaded bundles, then it can fallback when a translation is missing.

zbraniecki commented 1 year ago

hmm, maybe you're right. I don't have the complete mental model in my mind now. Maybe you need to adjust ResourceManager? And that may require a Localization that works with concurrent ResourceManager that produces concurrent FluentBundle.

ClementGre commented 1 year ago

I think the file bundles.rs should be edited cause it is the &Rc<Bundles<G>> returned by Localization::bundles(&self) that is not thread-safe (does not implement Send + Sync + 'static).

But I don't have the knowledge in Rust to do this so I'll wait for someone to fix the issue.