What can the differences and intended uses be for ioToST
and unsafeSTToIO
defined in GHC.IO?
-- ---------------------------------------------------------------------------
-- Coercions between IO and ST
-- | A monad transformer embedding strict state transformers in the 'IO'
-- monad. The 'RealWorld' parameter indicates that the internal state
-- used by the 'ST' computation is a special one supplied by the 'IO'
-- monad, and thus distinct from those used by invocations of 'runST'.
stToIO :: ST RealWorld a -> IO a
stToIO (ST m) = IO m
ioToST :: IO a -> ST RealWorld a
ioToST (IO m) = (ST m)
-- This relies on IO and ST having the same representation modulo the
-- constraint on the type of the state
--
unsafeIOToST :: IO a -> ST s a
unsafeIOToST (IO io) = ST $ \ s -> (unsafeCoerce# io) s
unsafeSTToIO :: ST s a -> IO a
unsafeSTToIO (ST m) = IO (unsafeCoerce# m)
The safe versions must start in the IO monad (because you cannot obtain an ST RealWorld
from runST
) and allow you to switch between the IO context and a ST RealWorld
context. They are safe because ST RealWorld
is basically the same thing as IO.
The unsafe versions can start anywhere (because runST
can be called anywhere) and allow you to switch between an arbitrary ST monad and the IO monad. Using runST
from a pure context and then doing a unsafeIOToST
within the state monad is basically equivalent to using unsafePerformIO
.
TL;DR. All four of these functions are just typecasts. They are all no-op at run-time. The only difference between them is the type signatures — but it's the type signatures that enforce all the safety guarantees in the first place!
The ST
monad and the IO
monad both give you mutable state.
It is famously impossible to escape the IO
monad. [Well, no, you can if you use unsafePerformIO
. Don't do that!] Because of this, all the I/O that your program will ever perform gets bundled up into a single giant IO
block, thus enforcing a global ordering on the operations. [At least, until you call forkIO
, but anyway...]
The reason unsafePerformIO
is so damned unsafe is that there is no way to figure out exactly when, if, or how many times the enclosed I/O operations will happen — which is typically a very bad thing.
The ST
monad also provides mutable state, but it does have an escape mechanism — the runST
function. This lets you turn an impure value into a pure one. But now there is no way to guarantee what order separate ST
blocks will run in. In order to prevent complete devastation, we need to ensure that separate ST
blocks can't "interfere" with each other.
For that reason, you can't perform any I/O operations in the ST
monad. You can access mutable state, but that state isn't allowed to escape the ST
block.
The IO
monad and the ST
monad are actually the same monad. And an IORef
is actually an STRef
, and so on. So it would really by jolly useful to be able to write code and use it in both monads. And all four of the functions you mention are type-casts that let you do exactly that.
To understand the danger, we need to understand how ST
achieves it's little trick. It's all in the phantom s
type in the type signatures. To run an ST
block, it needs to work for all possible s
:
runST :: (forall s. ST s x) -> x
All the mutable stuff has s
in the type as well, and by a happy accident, that means that any attempt to return mutable stuff out of the ST
monad will be ill-typed. (This is really a bit of a hack, but it works so perfectly...)
At least, it will be ill-typed if you use runST
. Notice that ioToST
gives you an ST RealWorld x
. Roughly speaking, IO x
≈ ST RealWorld x
. But runST
won't accept that as input. So you can't use runST
to run I/O.
The ioToST
gives you a type that you can't use with runST
. But unsafeIOToST
gives you a type that works just fine with runST
. At that point, you have basically implemented unsafePerformIO
:
unsafePerformIO = runST . ioToST
The unsafeSTToIO
allows you to get mutable stuff out of one ST
block, and potentially into another:
foobar = do
v <- unsafeSTToIO (newSTRef 42)
let w = runST (readSTRef v)
let x = runST (writeSTRef v 99)
print w
Wanna take a guess what is going to get printed? Because the thing is, we've got three ST
actions here, which can happen in absolutely any order. Will the readSTRef
happen before or after the writeSTRef
?
[Actually, in this example, the write never happens, because we don't "do" anything with x
. But if I pass x
to some distant, unrelated part of the code, and that code happens to inspect it, suddenly our I/O operation does something different. Pure code shouldn't be able to affect mutable stuff like that!]
Edit: It appears I was slightly premature. The unsafeSTToIO
function allows you to take a mutable value out of the ST
monad, but it appears it requires a second call to unsafeSTToIO
to put the mutable thing back into the ST
monad again. (At that point, both actions are IO
actions, so their ordering is guaranteed.)
You could of course mix in some unsafeIOToST
as well, but that doesn't really prove that unsafeSTToIO
by itself is unsafe:
foobar = do
v <- unsafeSTToIO (newSTRef 42)
let w = runST (unsafeIOToST $ unsafeSTToIO $ readSTRef v)
let x = runST (unsafeIOToST $ unsafeSTToIO $ writeSTRef v 99)
print w
I've played around with this, and I haven't yet managed to convince the type checker to let me do something provably unsafe using only unsafeSTToIO
. I remain convinced it can be done, and the various comments on this question seem to agree, but I can't actually construct an example. You get the idea though; change the types, and your safety gets broken.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With