Alternative Design for Finalisation

Simon Marlow simonmar@microsoft.com
Thu, 20 Sep 2001 12:56:55 +0100


> If ForeignPtrs work the way I think they do, then I'm=20
> surprised they're=20
> designed as pointers. I believe the 'pointer' functionality=20
> is orthogonal=20
> to the 'finalisable' functionality and should be separated like this:
>=20
> --
> data Finalisable a	-- abstract handle to finalisable object
> instance Eq (Finalisable a);
> newFinalisable      :: a             -> IO () -> IO (Finalisable a);
> addFinaliser        :: Finalisable a -> IO () -> IO ();=20
> withFinalisable     :: Finalisable a -> (a -> IO b) -> IO b;
> touchFinalisable    :: Finalisable a -> IO ();
> finalisableContents	:: Finalisable a -> a;
>=20
> type ForeignPtr a =3D Finalisable (Ptr a);
> newForeignPtr          :: Ptr a        -> IO () -> IO (ForeignPtr a);
> newForeignPtr =3D newFinalisable;
> addForeignPtrFinalizer :: ForeignPtr a -> IO () -> IO () ;
> addForeignPtrFinalizer =3D addFinaliser;
> withForeignPtr         :: ForeignPtr a -> (Ptr a -> IO b) -> IO b;
> withForeignPtr =3D withFinalisable;
> touchForeignPtr        :: ForeignPtr a -> IO ();
> touchForeignPtr =3D touchFinalisable;
> foreignPtrToPtr	       :: ForeignPtr a -> Ptr a;
> foreignPtrToPtr =3D 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 =3D 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 =3D 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=20
> used as FFI=20
> arguments. But I believe that's purely syntactic sugar for=20
> 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