How can I make manually allocated data to collect garbage in Haskell?

I think that FFI calls some C functions from Haskell.

If the memory buffer is used to store some data and is allocated manually, and then it is used in Haskell calculations, can I somehow rely on the garbage collector to free it when it is no longer needed.

As for manual distributions, there are basically two ways (but the difference does not seem significant to my question):

  • allocating a buffer in Haskell and then passing it to the C function, as in fdRead
  • allocating a buffer in C (with malloc , as in GNU asprintf ), and then returning a pointer to Haskell

In both examples ( fdRead or asprintf ), there is also a problem that the data type stored in the buffer is not suitable for the Haskell program, therefore it is copied and converted for use in Haskell (with peekCString ). (I will put the code below.) After the copying and conversion takes place, the buffer is freed (in both cases).

However, I am thinking of a more efficient interface where Haskell will use the data directly, since it is stored by the C function (without conversion). (I have not yet studied, say, alternative implementations of String and related functions: is there one among them that can work directly with some C strings.)

If I follow this route, then there is one global problem: how to control the deletion of allocated buffers. (For functions without side effects — other than highlighting — I could even wrap calls in unsafePerformIO or declare them so that they are not IO .)

Convert and Immediate Free Examples

Haskell selection:

fdRead (here allocaBytes should take care of releasing):

 -- ----------------------------------------------------------------------------- -- fd{Read,Write} -- | Read data from an 'Fd' and convert it to a 'String' using the locale encoding. -- Throws an exception if this is an invalid descriptor, or EOF has been -- reached. fdRead :: Fd -> ByteCount -- ^How many bytes to read -> IO (String, ByteCount) -- ^The bytes read, how many bytes were read. fdRead _fd 0 = return ("", 0) fdRead fd nbytes = do allocaBytes (fromIntegral nbytes) $ \ buf -> do rc <- fdReadBuf fd buf nbytes case rc of 0 -> ioError (ioeSetErrorString (mkIOError EOF "fdRead" Nothing Nothing) "EOF") n -> do s <- peekCStringLen (castPtr buf, fromIntegral n) return (s, n) -- | Read data from an 'Fd' into memory. This is exactly equivalent -- to the POSIX @ read@ function. fdReadBuf :: Fd -> Ptr Word8 -- ^ Memory in which to put the data -> ByteCount -- ^ Maximum number of bytes to read -> IO ByteCount -- ^ Number of bytes read (zero for EOF) fdReadBuf _fd _buf 0 = return 0 fdReadBuf fd buf nbytes = fmap fromIntegral $ throwErrnoIfMinus1Retry "fdReadBuf" $ c_safe_read (fromIntegral fd) (castPtr buf) nbytes foreign import ccall safe "read" c_safe_read :: CInt -> Ptr CChar -> CSize -> IO CSsize 

selection in C

getValue.c :

 #define _GNU_SOURCE #include <stdio.h> #include "getValue.h" char * getValue(int key) { char * value; asprintf(&value, "%d", key); // TODO: No error handling! // If memory allocation wasn't possible, or some other error occurs, these functions will // return -1, and the contents of strp is undefined. return value; } 

GetValue.hs (here I explicitly call free after the conversion is done ):

 {-# LANGUAGE ForeignFunctionInterface #-} import Foreign hiding (unsafePerformIO) import Foreign.Ptr import Foreign.C.Types import Foreign.C.String(peekCString) import System.IO.Unsafe getValue :: Int -> IO String getValue key = do valptr <- c_safe_getValue (fromIntegral key) value <- peekCString valptr c_safe_free valptr return value foreign import ccall safe "getValue.h getValue" c_safe_getValue :: CInt -> IO (Ptr CChar) foreign import ccall safe "stdlib.h free" c_safe_free :: Ptr a -> IO () value :: Int -> String value = unsafePerformIO . getValue -- getValue has no side-effects, so we wrap it. {- A simple test: -} main1 = putStrLn (value 2) {- A test with an infinite list, which employs laziness: -} keys = [-5..] results = map value keys main = foldr (>>) (return ()) (map putStrLn (take 20 results)) 

If there was no (inefficient) conversion and copy step, I would have to rely on the garbage collector to free up, but I have no idea how to define such things in Haskell.

+6
source share
1 answer

The ForeignPtr type acts like a Ptr with a finalizer attached. When ForeignPtr receives the garbage collection, the finalizer completes and can call side C to release the pointer using the appropriate function.

Since the pointer is no longer available from Haskell, this is usually the right time to release it.

+2
source

Source: https://habr.com/ru/post/982601/


All Articles