(In search of some XY that may lead to as solution or workaround...)
If the caller chooses the T, and there are no further restrictions, where does the FnOnce() -> T that was required to create the JoinHandle<T> come from?
To more directly address the question, you have to get rid of the type generic on the method. Sometimes this is possible by type-erasing the generic, e.g.
Unless I missed it, the docs don't even tell us what that type param is. If I had to make a WAG, I'd say it was the type of the return value of the thread - if there is such a thing.
As you can see in the signature of spawn there are two constraints on both the closure given to spawn and its return value, let’s explain them:
The 'static constraint means that the closure and its return value must have a lifetime of the whole program execution. The reason for this is that threads can outlive the lifetime they have been created in.Indeed if the thread, and by extension its return value, can outlive their caller, we need to make sure that they will be valid afterwards, and since we can’t know when it will return we need to have them valid as long as possible, that is until the end of the program, hence the 'static lifetime.
The Send constraint is because the closure will need to be passed by value from the thread where it is spawned to the new thread. Its return value will need to be passed from the new thread to the thread where it is joined. As a reminder, the Send marker trait expresses that it is safe to be passed from thread to thread. Sync expresses that it is safe to have a reference be passed from thread to thread.