calloc
can fail
We start with the C code first, since it's somewhat shorter. Aside from the name bar
, we see one problem: calloc
might return 0
. Therefore, we should check that:
double* bar(int n, double *arr){
double* y;
y = (double*) calloc(n, sizeof(double));
if(!y) {
return 0;
}
for(int i=0; i<n; i++){
y[i] = arr[i]+1;
}
return y;
}
Who frees the memory?
However, there is still one problem. A user needs to know how add_one
conjured that memory in order to use the correct free
variant. That's something that can be handled with documentation. Still, there is no need for us to call calloc
, we can just give the responsibility to the user:
double* add_one(double * dest, const double * src, size_t count){
for(size_t i = 0; i < count; ++i) {
dest[i] = src[i] + 1;
}
return dest;
}
Note that this uses const
to make sure that we don't overwrite src
by accident. We also use size_t
to be ready for any kind of size the user might throw at us. The user is now responsible to allocate the memory, so they will also know how to free it.
We return the pointer because we followed memcpy
's interface, but we could also just make it a void
function instead.
Haskell part
Let's go to Haskell. We can see that there is something off: you import newForeignPtr
, but you use newForeignPtr_
. That's probably since newForeignPtr
needs the aforementioned free
function. That's in Foreign.Marshal.Alloc
:
fptr <- newForeignPtr finalizerFree ptr
But before we delve into that, let us take a step back and look at the foreign import:
foreign import ccall "bar" c_bar :: CInt -> Ptr CDouble -> Ptr CDouble
Unfortunately, your c_bar
does not have that type. Remember, whenever you see A -> B
in a function, you should always get the same B
if you've used the same A
:
all (== foo a) [foo a,foo a,foo a,foo a,foo a] -- all the same
But that does not hold for your function. It has a side-effect: every call will return another Ptr CDouble
. We have to put it in IO
:
foreign import ccall "bar" c_bar :: CInt -> Ptr CDouble -> IO (Ptr CDouble)
Note that you can use the unsafe
keyword here. It basically means that bar
will never call any Haskell function and will stay in its C world. With this, we would end up with
haskellBar :: SV.Vector CDouble -> IO (SV.Vector CDouble)
haskellBar sv = do
let n = SV.length sv
ptr <- SV.unsafeWith sv (c_bar (fromIntegral n))
fptr <- newForeignPtr finalizerFree ptr
return $ SV.unsafeFromForeignPtr0 fptr n
Which does not differ from your variant too much.
The add_one
variant is similar:
foreign import ccall unsafe "add_one" c_addOne :: Ptr CDouble -> Ptr CDouble -> CSize -> IO (Ptr CDouble)
It's in IO
, it uses unsafe
to be a little bit faster (but must not call Haskell from C), and it uses the correct types. It's wrapper is almost the same, but this time, we allocate the memory in Haskell:
addOne :: SV.Vector CDouble -> IO (SV.Vector CDouble)
addOne sv = do
fptr <- mallocForeignPtrArray n
SV.unsafeWith sv $ \v -> withForeignPtr fptr $ \f -> c_addOne f v (fromIntegral n)
return $ SV.unsafeFromForeignPtr0 fptr n
where
n = SV.length sv
The mallocForeignPtrArray
makes sure that the memory gets freed correctly.