How to use fmt inside multi-threaded futures?

Consider this:

https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=f2f51e3657bc49ab8578a8c4018f09ae

It fails with:

error[E0277]: `*mut (dyn std::ops::Fn() + 'static)` cannot be shared between threads safely
  --> src/main.rs:12:89
   |
12 |       async fn write_fmt<'args>(&mut self, args: fmt::Arguments<'args>) -> io::Result<()> {
   |  _________________________________________________________________________________________^
13 | |         let string = format!("{}", args);
14 | |         self.write_all(string.as_bytes()).await
15 | |     }
   | |_____^ `*mut (dyn std::ops::Fn() + 'static)` cannot be shared between threads safely
   |
   = help: within `[std::fmt::ArgumentV1<'_>]`, the trait `std::marker::Sync` is not implemented for `*mut (dyn std::ops::Fn() + 'static)`
   = note: required because it appears within the type `std::marker::PhantomData<*mut (dyn std::ops::Fn() + 'static)>`
   = note: required because it appears within the type `core::fmt::Void`
   = note: required because it appears within the type `&core::fmt::Void`
   = note: required because it appears within the type `std::fmt::ArgumentV1<'_>`
   = note: required because it appears within the type `[std::fmt::ArgumentV1<'_>]`
   = note: required because of the requirements on the impl of `std::marker::Send` for `&[std::fmt::ArgumentV1<'_>]`
   = note: required because it appears within the type `std::fmt::Arguments<'_>`
   = note: required because it appears within the type `[static generator@src/main.rs:12:89: 15:6 _self:&mut tokio::io::stdout::Stdout, args:std::fmt::Arguments<'_> for<'r, 's, 't0, 't1, 't2, 't3, 't4, 't5, 't6> {&'r mut tokio::io::stdout::Stdout, std::fmt::Arguments<'s>, std::string::String, &'t0 mut tokio::io::stdout::Stdout, &'t1 mut tokio::io::stdout::Stdout, &'t2 std::string::String, std::string::String, &'t3 [u8], &'t4 [u8], tokio::io::util::write_all::WriteAll<'t5, tokio::io::stdout::Stdout>, tokio::io::util::write_all::WriteAll<'t6, tokio::io::stdout::Stdout>, ()}]`
   = note: required because it appears within the type `std::future::GenFuture<[static generator@src/main.rs:12:89: 15:6 _self:&mut tokio::io::stdout::Stdout, args:std::fmt::Arguments<'_> for<'r, 's, 't0, 't1, 't2, 't3, 't4, 't5, 't6> {&'r mut tokio::io::stdout::Stdout, std::fmt::Arguments<'s>, std::string::String, &'t0 mut tokio::io::stdout::Stdout, &'t1 mut tokio::io::stdout::Stdout, &'t2 std::string::String, std::string::String, &'t3 [u8], &'t4 [u8], tokio::io::util::write_all::WriteAll<'t5, tokio::io::stdout::Stdout>, tokio::io::util::write_all::WriteAll<'t6, tokio::io::stdout::Stdout>, ()}]>`
   = note: required because it appears within the type `impl std::future::Future`
   = note: required because it appears within the type `impl std::future::Future`
   = note: required for the cast to the object type `dyn std::future::Future<Output = std::result::Result<(), std::io::Error>> + std::marker::Send`

This is because fmt::Arguments are !Send and !Sync. This means they cannot even be put in a mutex. So how do I use them in multi-threaded futures? Note that without fmt::Arguments it is impossible to use fmt::Debug and fmt::Display.

What I would do is create a logger and an MPSC channel, make the logger be the consumer of the channel, and then have every async fn simply clone a producer endpoint of the channel when it's initialized.

That way, each async fn can request a log to take place by sending a mag to the logger, rather than logging the msg itself.

As a bonus it can actually turn out to be faster then direct logging because you can control when you flush stdio and/or stderr.

What do I send through the channel?

I cannot send the whole owned object, cause it would move the object away.

I cannot borrow the object, cause either 'static would be required or I would have to hang my future.

I cannot send a formatted string or format arguments, because invoking format! and format_args! is the whole source of errors.

Try dropping them before calling await.

Sorry, drop who? where?

this didn't work

https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=ef777387a7a7f7017bea7970415f95d4

You want the Arguments value to be not stored in the future, so it needs to be dropped before it reaches an await point. I suppose in this case the issue is that it exists during the await point that always implicitly exists at the start of the function, so you should probably switch to a non-async function that returns an impl Future and convert the arguments to a string, then move the string into an async block, which you can then return.

Since it's in a trait, you will need an Pin<Box<dyn Future>> instead of impl Future.

1 Like

Oh, this works! https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=dd2033dea87f9dd14472dba789325f24

What won't work is this: https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=a2501f86f51e5913830461f7d76b409f

I guess it is possible to make a wrapper over write!

just to finish: this is what ended up working for me!

https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=dac845f500613f7959f8d995ce51b0f3

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.