A small module that allows threads to return their result
What you are experiencing is a common problem that occurs with threads and lazy evaluation. What happens is that the thread creates only an unevaluated thunk, which is passed through RThread to the main thread, and evaluated only then, and the exception is thrown there. This is also a very good example why using exceptions in pure values is a bad idea (but of course if you're making a library like this you have to take this possibility into account).

The solution is to fully evaluate a value inside the try block, so that any exception that might occur is thrown there. For this we need a combination of two functions: evaluate and force. The former creates an IO action that forces the evaluation, the latter one performs deep evaluation (to a normal form, not just to a weak head normal form).

Also you might also want to separate the return value from the thread action. If you're not going to operate on thread actions, you can even omit them from the data type, as once you start an action, all you need is the MVar channel.

An updated solution could look like this:

import Control.Applicative
import Control.Concurrent
import Control.DeepSeq
import Control.Exception

newtype RValue a = RValue (MVar (Either SomeException a))

returnFork :: (NFData a) =&gt; IO a -&gt; IO (RValue a)
returnFork act = do
    chan &lt;- newEmptyMVar
    forkIO $ try (act &gt;&gt;= evaluate . force) &gt;&gt;= putMVar chan
    return $ RValue chan

waitForResult :: RValue a -&gt; IO (Either SomeException a)
waitForResult (RValue chan) = takeMVar chan

main = returnFork (return $ Just (1 `div` 0 :: Int)) &gt;&gt;= waitForResult &gt;&gt;= print