Transform a vector with the FFI in Haskell
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&lt;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 &lt; 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 &lt;- 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 -&gt; Ptr CDouble -&gt; Ptr CDouble


Unfortunately, your c_bar does not have that type. Remember, whenever you see A -&gt; 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 -&gt; Ptr CDouble -&gt; 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 -&gt; IO (SV.Vector CDouble)
haskellBar sv = do
    let n = SV.length sv
    ptr &lt;- SV.unsafeWith sv (c_bar (fromIntegral n))
    fptr &lt;- 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 -&gt; Ptr CDouble -&gt; CSize -&gt; 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 -&gt; IO (SV.Vector CDouble)
addOne sv = do
   fptr &lt;- mallocForeignPtrArray n
   SV.unsafeWith sv $ \v -&gt; withForeignPtr fptr $ \f -&gt; 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.