-- | Memory management utilities.
module Hercules.CNix.Memory
( -- * Free after use
Delete (..),
withDelete,
-- * Free on GC
Finalizer (..),
toForeignPtr,
-- * Nullable pointers
forNonNull,
traverseNonNull,
)
where
import Foreign (FinalizerPtr, ForeignPtr, newForeignPtr, nullPtr)
import Protolude
-- | Types whose memory / resources can be freed in a consistent way.
class Delete a where
delete :: Ptr a -> IO ()
-- | Obtain a pointer to a resource and run an action with it.
withDelete :: (Delete a) => IO (Ptr a) -> (Ptr a -> IO b) -> IO b
withDelete make = bracket make delete
-- | Like 'Delete', but the design of finalizers favors that we implement it
-- by means of a function pointer instead of a Haskell function. That way, it
-- can be run during GC, without the need for a separate thread and such.
--
-- NOTE: This should always return a CAF, to avoid repeated allocation,
-- initialization, etc.
--
-- Example:
--
-- @
-- instance Finalizer CStdString where
-- finalizer = finalize
--
-- finalize :: FinalizerPtr CStdString
-- {-# NOINLINE finalize #-}
-- finalize =
-- unsafePerformIO
-- [C.exp|
-- void (*)(std::string *) {
-- [](std::string *v) {
-- delete v;
-- }
-- }
-- |]
-- @
class Finalizer a where
finalizer :: FinalizerPtr a
-- | Construct a 'ForeignPtr' using 'finalizer'.
-- This takes ownership of the pointer, so it must only be called once per pointer.
toForeignPtr :: (Finalizer a) => Ptr a -> IO (ForeignPtr a)
toForeignPtr ptr = newForeignPtr finalizer ptr
-- Pointer utilities
-- | Turn an action on pointer into an action that returns 'Nothing' iff the pointer is 'nullPtr'.
--
-- Same as 'flip' 'forNonNull'.
traverseNonNull :: (Applicative m) => (Ptr a -> m b) -> Ptr a -> m (Maybe b)
traverseNonNull f p = if p == nullPtr then pure Nothing else Just <$> f p
-- | Run an action with a pointer, if it is not 'nullPtr'.
--
-- Same as 'flip' 'traverseNonNull'.
forNonNull :: (Applicative m) => Ptr a -> (Ptr a -> m b) -> m (Maybe b)
forNonNull = flip traverseNonNull