Help making this lazy_static Rc RefCell code compile

Broken code:


pub enum Gui_Log_Msg {
    String(String),
}

pub enum Gui_Logger_Inner {
    Msgs(Vec<Gui_Log_Msg>),
    Handler(Rc<dyn Fn(Gui_Log_Msg)>),
}

pub struct Gui_Logger {
    inner: Rc<RefCell<Gui_Logger_Inner>>,
}

impl Gui_Logger {
    pub fn log(&self, msg: Gui_Log_Msg) {
        let mut b = self.inner.borrow_mut();
        match b.deref_mut() {
            Gui_Logger_Inner::Msgs(msgs) => {
                msgs.push(msg);
            }
            Gui_Logger_Inner::Handler(f) => {
                f(msg);
            }
        }
    }

    pub fn set_handler(&self, f: Rc<dyn Fn(Gui_Log_Msg)>) {
        let b = self.inner.replace(Gui_Logger_Inner::Handler(f.clone()));
        match b {
            Gui_Logger_Inner::Msgs(msgs) => {
                for x in msgs {
                    f(x);
                }
            }
            Gui_Logger_Inner::Handler(_) => {}
        }
    }

    pub fn new() -> Gui_Logger {
        Gui_Logger::Msgs(vec![])
    }
}

lazy_static! {
    static ref global_logger: Gui_Logger = Gui_Logger::new();
}

pub struct Gui_Log {}

impl Gui_Log {
    pub fn log(msg: Gui_Log_Msg) {
        global_logger.log(msg);
    }

    pub fn set_handler(f: Rc<dyn Fn(Gui_Log_Msg)>) {
        global_logger.set_handler(f)
    }
}

EDIT: modified code to simplify

Context: I want to build a global logger, but one that is initialized at runtime. So I need lazy_static to create a Singleton of sorts.

Here is a playground from OP's snippet which generates the following error message:

error[E0277]: `Rc<RefCell<Gui_Logger_Inner>>` cannot be shared between threads safely
  --> src/lib.rs:51:1
   |
51 | / lazy_static! {
52 | |     static ref global_logger: Gui_Logger = Gui_Logger::new();
53 | | }
   | |_^ `Rc<RefCell<Gui_Logger_Inner>>` cannot be shared between threads safely
   |
   = help: within `Gui_Logger`, the trait `Sync` is not implemented for `Rc<RefCell<Gui_Logger_Inner>>`
note: required because it appears within the type `Gui_Logger`
  --> src/lib.rs:17:12
   |
17 | pub struct Gui_Logger {
   |            ^^^^^^^^^^
note: required by a bound in `Lazy`
  --> /playground/.cargo/registry/src/index.crates.io-6f17d22bba15001f/lazy_static-1.4.0/src/inline_lazy.rs:19:20
   |
19 | pub struct Lazy<T: Sync>(Cell<Option<T>>, Once);
   |                    ^^^^ required by this bound in `Lazy`
   = note: this error originates in the macro `__lazy_static_create` which comes from the expansion of the macro `lazy_static` (in Nightly builds, run with -Z macro-backtrace for more info)

For more information about this error, try `rustc --explain E0277`.
error: could not compile `playground` (lib) due to previous error

The thread-safe version of Rc<RefCell<T>> is Arc<Mutex<T>>, so I'd try those types instead:

#![allow(non_camel_case_types)]
#![allow(non_upper_case_globals)]

use lazy_static::lazy_static;

use std::mem;
use std::sync::{Arc, Mutex};

pub enum Gui_Log_Msg {
    String(String),
}

pub enum Gui_Logger_Inner {
    Msgs(Vec<Gui_Log_Msg>),
    Handler(Arc<dyn Fn(Gui_Log_Msg) + Send + Sync>),
}

pub struct Gui_Logger {
    inner: Arc<Mutex<Gui_Logger_Inner>>,
}

impl Gui_Logger {
    pub fn log(&self, msg: Gui_Log_Msg) {
        let mut b = self.inner.lock().unwrap();
        match *b {
            Gui_Logger_Inner::Msgs(ref mut msgs) => {
                msgs.push(msg);
            }
            Gui_Logger_Inner::Handler(ref f) => {
                f(msg);
            }
        }
    }

    pub fn set_handler(&self, f: Arc<dyn Fn(Gui_Log_Msg) + Send + Sync>) {
        let mut lock = self.inner.lock().unwrap();

        let b = mem::replace(&mut *lock, Gui_Logger_Inner::Handler(f.clone()));

        match b {
            Gui_Logger_Inner::Msgs(msgs) => {
                for x in msgs {
                    f(x);
                }
            }
            Gui_Logger_Inner::Handler(_) => {}
        }
    }

    pub fn new() -> Gui_Logger {
        Gui_Logger {
            inner: Arc::new(Mutex::new(Gui_Logger_Inner::Msgs(vec![]))),
        }
    }
}

lazy_static! {
    static ref global_logger: Gui_Logger = Gui_Logger::new();
}

pub struct Gui_Log {}

impl Gui_Log {
    pub fn log(msg: Gui_Log_Msg) {
        global_logger.log(msg);
    }

    pub fn set_handler(f: Arc<dyn Fn(Gui_Log_Msg) + Send + Sync>) {
        global_logger.set_handler(f)
    }
}

Playground.

2 Likes

Could you explain a bit more on the + Send + Sync ?

From my limited understanding, aren't these traits by default implemented?

It is possible to have a type that implements Fn(Gui_Log_Msg) without also implementing Send, so dyn Fn(Gui_Log_Msg) can't be Send.

1 Like

If a closure captures something that is not Send or Sync, it won't be Send or Sync either. Example. To make sure we don't allow such closures—which would break our thread-safe api—we need to add the Send + Sync trait bounds.

1 Like

Are the following statements correct:

  1. Some types are +Send +Sync , some are not (ex: RefCell).

  2. All the types the Rust compiler can infer to be +Send +Sync, automatically have a +Send +Sync

  3. A Fn() could be NOT +Send +Sync (it could move a Rc<RefCell>)

  4. Our overall struct has to be +Send +Sync, so here we add an extra promise of : I promise this Fn(...) is +Send +Sync ?

Is this what is going on ?

Thanks!

Pretty much, though I'd say the second statement is a little imprecise, or rather I wouldn't use the word infer here. The compiler automatically implements Send and Sync for your type if it is composed entirely of Send and Sync types:

Send and Sync are also automatically derived traits. This means that, unlike every other trait, if a type is composed entirely of Send or Sync types, then it is Send or Sync. Almost all primitives are Send and Sync, and as a consequence pretty much all types you'll ever interact with are Send and Sync.

Send and Sync (and a few other traits) are known as auto traits because the compiler automatically implements them for your type if it is able to and unless you explicitly opt out of that implementation.

3 Likes

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.