unsafePerformIO around FFI calls

C.Reinke C.Reinke@ukc.ac.uk
Wed, 10 Jul 2002 14:00:22 +0100


> > I'm curious exactly what is "safe" and what is "unsafe" to wrap
> > unsafePerformIO around when it comes to FFI calls. 
> 
> Here's a simple test:
> 
>  Could you imagine an alternative implementation of the same API in
>  pure Haskell?  (Don't consider efficiency or effort required to write
>  the implementation, just whether it can be done.)
> 
>  If so, then it is ok to use unsafePerformIO and the ffi to implement
>  the API instead.

That looks simple, but is not unproblematic: 

- just because I can *imagine* a pure implementation of *the API I want*, 
  that doesn't mean that the foreign implementation in question *is* pure
  (*the exact API it implements* might have some extra features or
   assumptions, such as single-threaded use).

- so the test implicitly depends on a very strict interpretation of 
  "the same API", including all observable side-effects, and that 
  isn't quite as simple as you make it.

> If it fails that test, it is incredibly unlikely that it is ok and a
> proof that it is ok is likely to be pretty complex - maybe worth a
> PLDI paper or some such.

- just because I can't implement something in pure Haskell doesn't 
  mean that it has to be impure, or unsafe (it might fall into the 
  gaps of static typing, for instance).

So it seems to boil down to the usual argument about
unsafePerformIO, no matter whether the IO action is foreign or not:
by casting from "IO a" to "a", you *assert* that the IO part of your
computation is not observable, and it is *your obligation to show*
that this assertion is correct. You don't have to do a formal proof,
but while attempting an informal one, you might find
*side-conditions* (on the usage of your function) on which your
assertion depends. The most valuable part of the proof attempts is
to *identify and document*, *for your particular case*, those
side-conditions. In other words, to become aware of "exactly what is
`safe' and what is `unsafe' to wrap unsafePerformIO around"!-)

  Can your implementation defend itself against all attempts to 
  discover that its "a" is really an "IO a"?

  If so, you're safe (or feel so, at least;-). If not, document the
  gaps in your defense (they might not arise in typical or intended
  use). If there are too many or too serious gaps, you're definitely 
  "unsafe".

Beware of optimizing compilers or other meaning-preserving
program-transformation tools. The more you want your tools to make
use of the semantics of your programs, the less you want to cheat on
those semantics (is your foreign implementation still pure in a
multi-threaded setting?). It's not called unsafePerformIO for
nothing - using it means "trust me, I know what I'm doing". Do you?

As for FFI-specifics, the problem seems to *find out* what the
*precise API* of your foreign function is (including side-effects),
and what *extra impurities* the FFI wrapping might impose. Given
that the foreign function is imported as "IO a", the FFI wrapping
would be permitted to use "IO", e.g., during marshalling. Does it?

Cheers,
Claus