With the Haskell C FFI you have to give the signature of each imported function yourself, so you can just choose to use IO or not.
Additionally, binding writers might expose a higher level pure interface on top of low level IO functions. In that case they can use unsafePerformIO
if they are sure that the interface is really pure.