Kamil Janowski
Kamil Janowski

Reputation: 2025

Sharing a variable with a closure

I know, the answers for similar questions already exist... kinda... all that I found were very high level instructions rather than code examples and I couldn't really follow them and I've been stuck in place for days now...

I need to share a variable with a closure. So I have my MockSlave struct. I want it to create a vector that can then be shared with the shell closure. The current version looks something like this

impl MockSlave {

    pub async fn new(port: i32) -> Result<Self, Box<dyn std::error::Error>> {
        let addr = String::from(format!("https://127.0.0.1:{}", port).as_str());
        let endpoint = Endpoint::from_str(addr.as_str())?;

        let commands = Rc::new(RefCell::new(Vec::new()));

        let client = SlaveClient::new(endpoint, CommandProcessors { 
            shell: Some(|command| {
                commands.borrow_mut().push(command);
                Ok(())
            })
        }).await?;

        Ok(MockSlave {
            client,
            commands
        })
    }

For clarity, here's SlaveClient::new

pub async fn new(endpoint: Endpoint, processors: CommandProcessors) -> Result<Self, tonic::transport::Error>  {
        let slave = SlaveClient { 
            client: SlaveManagerClient::connect(endpoint).await?,
            processors
        };

        Ok(slave)
    }

And here's CommandProcessors:

pub struct ReadWriteStreamingCommand {
    pub command: Command
}

pub type ReadWriteSteamingCommandprocessor = fn(Box<ReadWriteStreamingCommand>) -> Result<(), Box<dyn Error>>;

pub struct CommandProcessors {
    pub shell: Option<ReadWriteSteamingCommandprocessor>
}

Now, the fun part is that VSCode and cargo build give me slightly different errors about the same thing.

VSCode says:

closures can only be coerced to `fn` types if they do not capture any variables

Meanwhile, cargo build says


error[E0308]: mismatched types
  --> tests/api_tests/src/mock_slave.rs:20:25
   |
20 |               shell: Some(|command| {
   |  _________________________^
21 | |                 commands.borrow_mut().push(command);
22 | |                 Ok(())
23 | |             })
   | |_____________^ expected fn pointer, found closure

Please help. This is at least my 10th approach to this problem over the course of a week. I am not going to move forward by myself...

Upvotes: 1

Views: 1318

Answers (1)

Silvio Mayolo
Silvio Mayolo

Reputation: 70257

fn(Box<ReadWriteStreamingCommand>) -> Result<(), Box<dyn Error>>;

This is not the type you think it is. This is the type of pointers to functions that exist statically in your code. That is, functions defined at the top-level or closures which don't actually close over any variables. To accept general Rust callables (including traditional functions and closures), you need one of Fn, FnMut, or FnOnce.

I can't tell from the small code you've shown which of the three you need, but the basic idea is this.

  • Use FnOnce if you are only going to call the function at most once. This is useful if you're writing some kind of "control flow" type of construct or mapping on the inside of an Option (where at most one value exists).

  • Use FnMut if you are going to call the function several times but never concurrently. This is what's used by most Rust built-in collection functions like map and filter, since it's always called in a single thread. This is the one I find myself using the most often for general-purpose things.

  • Use Fn if you need concurrent access and are planning to share across threads.

Every Fn is an FnMut (any function that can be called concurrently can be called sequentially), and every FnMut is an FnOnce (any function that can be called can obviously be called once), so you should choose the highest one on the list above that you can handle.

I'll assume you want FnMut. If you decide one of the others is better, the syntax is the same, but you'll just need to change the name. The type you want, then, is

dyn FnMut(Box<ReadWriteStreamingCommand>) -> Result<(), Box<dyn Error>>

But this is a trait object and hence is not Sized, so you can't do much with it. In particular, you definitely can't put it in another Sized data structure. Instead, we'll wrap it in a Box.

pub type ReadWriteSteamingCommandprocessor = 
   Box<dyn FnMut(Box<ReadWriteStreamingCommand>) -> Result<(), Box<dyn Error>>>;

And then

shell: Some(Box::new(|command| {
            commands.borrow_mut().push(command);
            Ok(())
        }))

Upvotes: 3

Related Questions