!Send functions

There are many situations in which a function within a module/one function within a larger Send struct etc. is not safe for multithreaded use. It would be nice it there was syntax to explicitly be able to mark a function as !Send.

It is generally data that isn't safe to send, not functions. Functions are just pointers to compiled code in the binary, which is static and const. As such they are safe to send. The only exceptions I can think of involve JITed code and dynamic loading/patching.

Do you have an example of what you mean? (You claim there are many such situations, so please share.)

1 Like

I don't know what @cheesycod has in mind specifically, but "not safe for multithreaded use" in general is a property that functions can have. It might be nice to be able to put that information into the type system, but "that information" is much more complicated than just Send vs !Send. The Linux manpages have an entire page about what the thread safety annotations at the bottom of each library function reference page mean, and those are not formal enough to be traits.

Also, even if we could put this information into the type system, I'm not sure what the compiler could usefully do with it. "Are other threads running in this process right now?" is a complicated question, and "did the application wrap this legacy non-thread-safe function with locks, and are those locks correct and sufficient?" is even more complicated.

It’s impossible to guess what use-case you have in mind.

A typical example for non-thread-safe “functions” is e.g. legacy/FFI libraries that work with unsynchronized global state. But you do bring up “one function within a larger Send struct”, which doesn’t fit this case at all. What is a “function within a struct”, even?

We just need… examples, examples, examples!

I can imagine multiple vastly different kind of scenarios you might have in mind already, and they vary from (1) easy to express using existing language features; over to (2) possibly desirable, but likely requiring complex new language features, which are hard to design, and even more so in a backwards compatible manner; and all the way to (3) just unsound (or unsafe & then very hard to reason about) API design which is simply undesirable, or which is at least coming with thread-safety rules that are very specific to the concrete use case and thus can’t hope for any language/compiler support.

The problem is that functions in Rust are not sent anywhere. They're globals accessible from everywhere. There's no data flow to track from where the function was created to where the function is used.

Functions belong to the executable, and aren't tied to any thread. They're all already created before threads existed.

There has to be some value that is created in a thread, and then its usage can be tracked to stop it from being moved where !Send is not allowed.

To make a function that is !Send, you would need to create some dummy !Send value that the function must take, and ensure the value can only be created on the correct thread (how to do that is going to be application-specific):

fn non_send_function(_token: &Token) {}

use std::marker::PhantomData;
struct Token(PhantomData<std::rc::Rc<()>>);

Functions are not thread-safe if they access globals without synchronization. In safe Rust, such functions are simply forbidden (they're unsafe). The solution to this is adding synchronization.

unsafe fn non_thread_safe() {…}

pub fn safe() {
    static LOCK: Mutex<()> = Mutex::new();
    let _lock = LOCK.lock();

    // this is safe if this function is the only place that calls it,
    // otherwise it's the same bug as Rust's set_env was.
    unsafe {
        non_thread_safe(); 
    }
}