
If ForeignPtrs work the way I think they do, then I'm surprised they're designed as pointers. I believe the 'pointer' functionality is orthogonal to the 'finalisable' functionality and should be separated like this:
-- data Finalisable a -- abstract handle to finalisable object instance Eq (Finalisable a); newFinalisable :: a -> IO () -> IO (Finalisable a); addFinaliser :: Finalisable a -> IO () -> IO (); withFinalisable :: Finalisable a -> (a -> IO b) -> IO b; touchFinalisable :: Finalisable a -> IO (); finalisableContents :: Finalisable a -> a;
type ForeignPtr a = Finalisable (Ptr a); newForeignPtr :: Ptr a -> IO () -> IO (ForeignPtr a); newForeignPtr = newFinalisable; addForeignPtrFinalizer :: ForeignPtr a -> IO () -> IO () ; addForeignPtrFinalizer = addFinaliser; withForeignPtr :: ForeignPtr a -> (Ptr a -> IO b) -> IO b; withForeignPtr = withFinalisable; touchForeignPtr :: ForeignPtr a -> IO (); touchForeignPtr = touchFinalisable; foreignPtrToPtr :: ForeignPtr a -> Ptr a; foreignPtrToPtr = finalisableContents;
Unfortunately it isn't possible to add a finalizer to a (Ptr a). We already have a generic finalization mechanism: see the Weak module in package lang. But the only reliable way to use finalizers(*) is to attach one to an atomic heap object - that way the compiler's optimiser can't interfere with the lifetime of the object. The Ptr type is really just a boxed address - it's defined like data Ptr a = Ptr Addr# where Addr# is an unboxed native address (just a 32- or 64- bit word). Putting a finalizer on a Ptr is dangerous, because the compiler's optimiser might remove the box altogether. ForeignPtrs are defined like this data ForeignPtr a = ForeignPtr ForeignObj# where ForeignObj# is a *boxed* address, it corresponds to a real heap object. The heap object is primitive from the point of view of the compiler - it can't be optimised away. So it works to attach a finalizer to the ForeignObj# (but not to the ForeignPtr!). There are several primitive objects to which we can attach finalizers: MVar#, MutVar#, ByteArray#, etc. We have special functions for some of these: eg. MVar.addMVarFinalizer. So a nicer interface might be something like class Finalizable a where addFinalizer :: a -> IO () -> IO () instance Finalizable (ForeignPtr a) where ... instance Finalizable (MVar a) where ... (apologies for the different spelling of finalize - apparently both are correct and I randomly settled on the 'z' version some time ago). So you might ask why we don't just get rid of Ptr and rename ForeignPtr to Ptr. The reason for that is just efficiency, I think.
The only time when ForeignPtrs act like Ptrs is when they are used as FFI arguments. But I believe that's purely syntactic sugar for withForeignPtr, and would be no loss.
Indeed, the latest version of the FFI spec dissallows ForeignPtrs as FFI arguments. Hope this all makes some sense. I'll copy the text into the GHC commentary if so. Cheers, Simon