{
  "$type": "site.standard.document",
  "bskyPostRef": {
    "cid": "bafyreic22pcshrskwwazkoo5uaobsza2ozcdceru4le5hsx3qj6bbpx3ja",
    "uri": "at://did:plc:pi6woz4d47bkuws673w2il2r/app.bsky.feed.post/3mejxvff5dyx2"
  },
  "path": "/t/botan-bindings-devlog/6855?page=10#post_195",
  "publishedAt": "2026-02-10T22:05:06.000Z",
  "site": "https://discourse.haskell.org",
  "tags": [
    "@a",
    "@a",
    "@a"
  ],
  "textContent": "# Update continued: Replacing `mkBindings` with `BotanObject`\n\nPicking up where we left off yesterday, its time for some code! We’re going to replace the `mkBindings` function with something more compact.\n\n### Minor aside - upgrading `Memorable`\n\nPer yesterday’s discussion, `mkBindings` takes 2 newtype constructors-getter pairs (one wraps a `Ptr`, the other a `ForeignPtr`), and a `FinalizerPtr` for the destructor. All of the botan context objects follow this pattern, so we’re going to just codify this as a typeclass, making it a lot easier to wrangle.\n\nPreparing today’s update involved making a few small changes to the `Memorable` class:\n\n\n    class ... => Memorable memo where\n        -- Added\n        type MemRep memo :: Type\n        -- Changed\n        withMem :: memo -> (Mem memo (MemRep memo) -> IO a) -> IO a\n\n\nWe have added a new `MemRep` associated type family, for the underlying memory type - so for `ByteString`, which is a `Ptr Word8`, `Mem` is `Ptr`, and `MemRep` is `Word8`, respectively. This allows us to not care whether a `Memory` type is monomorphic (eg, `ByteString`) or polymorphic (eg `ForeignPtr`). I had suspected that this would be necessary sooner or later, per earlier notes on the wildcard `a` parameter in `withMem`, which has now become fixed to `MemRep memo`.\n\n> NOTE: This change in general does have implications that I am still pondering, such as no longer being 1:1 drop-in compatible with `memory:ByteArrayAccess.withByteArray`, but I’d rather _allow non-castable memory types and require that you use`castPtr`_, than the inverse, and it seems so much more sensible that if we allocate a `ByteString` with a length corresponding to a number of bytes, then when we access the pointer of that bytestring _it should be a`Ptr Word8`_, and we should have to cast when we want anything else. It is more type-safe, and the longer I think about it, the more confused I am as to why `memory` did it that way.\n\n### Back to refactoring `Botan.Low.RNG`\n\nNow, we’re going to start with a bit of a fresh slate - no `Internal.ByteString` or `Make` or `Remake`, just the bindings and our new supporting `Memory` classes:\n\n\n    -- Used to define BotanObject\n    {-# LANGUAGE AllowAmbiguousTypes #-}\n\n    import Botan.Bindings.ConstPtr (ConstPtr (..))\n    import Botan.Bindings.RNG\n\n    import Control.Monad (void)\n    import Control.Exception (mask_)\n\n    import Data.Kind\n    import Data.ByteString (ByteString)\n\n    import Foreign.Ptr (Ptr)\n    import qualified Foreign.Ptr as Ptr\n    import qualified Foreign.Storable as Ptr\n\n    import Foreign.ForeignPtr (ForeignPtr, FinalizerPtr)\n    import qualified Foreign.ForeignPtr as ForeignPtr\n\n    import Foreign.C.Types (CInt)\n    import Foreign.Marshal.Alloc (alloca)\n\n    -- This vvv is really the only 'new' import\n    -- I could actually pull in even more but this suffices\n\n    import Memory.Memory\n    import Memory.Pointer\n\n\nThere are a few things to note immediately - because `RNG` and `BotanRNG` are just a `ForeignPtr` and a `Ptr` respectively, they actually conform to `Memorable`.\n\n`BotanRNG` gets an orphan instance since it is declared in `Botan.Bindings.RNG`.\n\n\n    instance Memorable BotanRNG where\n        type Mem BotanRNG = Ptr\n        type MemRep BotanRNG = BotanRNGStruct\n        withMem (MkBotanRNG ptr) action = action ptr\n\n\n\nOur definition of `RNG` hasn’t changed, and it gets a `Memorable` instance too.\n\n\n    newtype RNG = MkRNG { foreignPtr :: ForeignPtr BotanRNGStruct }\n\n    instance Memorable RNG where\n        type Mem RNG = ForeignPtr\n        type MemRep RNG = BotanRNGStruct\n        withMem (MkRNG fptr) action = action fptr\n\n\nNow comes the bit of the code where before we would use `mkBindings` to generate these functions:\n\n\n    withRNG     :: RNG -> (BotanRNG -> IO a) -> IO a\n    rngDestroy  :: RNG -> IO ()\n    createRNG   :: (Ptr BotanRNG -> IO CInt) -> IO RNG\n\n\nSince all these do is pack and unpack pointers and finalizers, we are going to codify it as a typeclass instead of an awkward function that returns functions - that means we need to take a look at `mkBindings` itself, to see how it works:\n\n\n    mkBindings\n        ::  (Storable botan)\n        =>  (Ptr struct -> botan)                                   -- mkBotan\n        ->  (botan -> Ptr struct)                                   -- runBotan\n        ->  (ForeignPtr struct -> object)                           -- mkForeign\n        ->  (object -> ForeignPtr struct)                           -- runForeign\n        ->  FinalizerPtr struct                                     -- destroy / finalizer\n        ->  (   object -> (botan -> IO a) -> IO a                   -- withObject\n            ,   object -> IO ()                                     -- destroyObject\n            ,   (Ptr botan -> IO CInt) -> IO object                 -- createObject\n            )\n    mkBindings mkBotan runBotan mkForeign runForeign destroy = bindings where\n        bindings = (withObject, objectDestroy, createObject)\n        newObject botan = do\n            foreignPtr <- newForeignPtr destroy (runBotan botan)\n            return $ mkForeign foreignPtr\n        withObject object f = withForeignPtr (runForeign object) (f . mkBotan)\n        objectDestroy object = finalizeForeignPtr (runForeign object)\n        createObject = mkCreateObject newObject\n\n    mkCreateObject\n        :: (Storable botan)\n        => (botan -> IO object)\n        -> (Ptr botan-> IO CInt)\n        -> IO object\n    mkCreateObject newObject init = mask_ $ alloca $ \\ outPtr -> do\n            throwBotanIfNegative_ $ init outPtr\n            out <- peek outPtr\n            newObject out\n\n\nI think it is about the most confusing code I have ever written. That’s because botan requires that we allocate a pointer to a pointer to an opaque struct*, that it fills, that we have to peek at, attach a finalizer to, and wrap it up, all while handling a potential allocation or initialization failure. Luckily we were fairly smart - we use `mask_` and `alloca` for the ptr-ptr, it is really just confusing as to when things are what, and that type definition is some horror upon the deep.\n\n> * Technically, a pointer to the CApiFFI-enforced newtype-wrapper over a pointer to an opaque struct, that we must first unwrap before rewrapping…\n\nSo lets clean that up with a little more of our recently-favorite hammer, `TypeFamilies`, shall we?\n\n> NOTE: Data families would also work, if we redefined `Bindings` and `Low` as a single module, and if `CApiFFI / CTYPE` allowed it (no idea if it does)\n\n\n    class\n        ( Memorable a\n        , Memorable (BotanPtr a)\n        , Mem a            ~ ForeignPtr\n        , Mem (BotanPtr a) ~ Ptr\n        , MemRep a            ~ BotanStruct a\n        , MemRep (BotanPtr a) ~ BotanStruct a\n        ) => BotanObject a where\n\n        type family BotanStruct a :: Type\n        type family BotanPtr    a :: Type\n\n        toBotanPtr :: Ptr (BotanStruct a) -> BotanPtr a\n        toBotan    :: ForeignPtr (BotanStruct a) -> a\n        botanFinalizer :: FinalizerPtr (BotanStruct a)\n\n        withBotanPtr :: a -> (BotanPtr a -> IO b) -> IO b\n        createBotan :: (Ptr (BotanPtr a) -> IO CInt) -> IO a\n        destroyBotan :: a -> IO ()\n\n\nLets take a moment to clarify the way this works: `BotanStruct Foo` now refers to `BotanFooStruct`, and `BotanPtr Foo` is now a `Memorable`, who’s mem is a `Ptr` and who’s rep is a `BotanFooStruct`. We just codified a relationship between the wrapper types such that the `Foo` type (which is a `Memorable ForeignPtr BotanFooStruct`) ties them all together., that’s all. It feels odd to declare a type family and then immediately force-constrain it, but remember, we’re actually constraining the corresponding `Mem` and `MemRep` types to be relevant to each other.\n\nThen, once you deal with your types, we can fill in the functions with reasonable defaults:\n\n\n    -- class BotanObject a continued\n\n        withBotanPtr :: a -> (BotanPtr a -> IO b) -> IO b\n        withBotanPtr botan action =\n            withMem botan $ \\ fptr -> do\n                withMem fptr $ \\ ptr -> do\n                    action (toBotanPtr @a ptr)\n\n        createBotan :: (Ptr (BotanPtr a) -> IO CInt) -> IO a\n        createBotan init = mask_ $ alloca $ \\ ptrPtr -> do\n            throwBotanIfNegative_ $ init (Ptr.castPtr ptrPtr) -- NOTE: Can be defined without this cast\n            ptr <- Ptr.peek ptrPtr\n            fptr <- ForeignPtr.newForeignPtr (botanFinalizer @a) ptr\n            return $ toBotan fptr\n\n        destroyBotan :: a -> IO ()\n        destroyBotan botan = withMem botan ForeignPtr.finalizeForeignPtr\n\n\nThe noted cast turns a `Ptr (Ptr BotanFooStruct)` into a `Ptr (BotanPtr Foo)` for the `init` method, which we could avoid by applying a `Ptr.Storable (BotanPtr a) ` constraint to the `BotanObject a` instead - we just cast it knowing that it is a newtype over the pointer we want. Then we just get the pointer out from the temporary ptr-ptr, stick it in a ForeignPtr with our finalizer, before finally returning the wrapped foreign pointer.\n\n> NOTE: Ideally I’d be doing something like `withMem ptrPtr $ \\ ptr -> throwBotanIfNegative_ $ init (toBotanPtr @a ptr)` but (as part of the aforementioned repercussions of de-wilding `withMem`) the `withMem` implementation for `Ptr` is currently simply `id` meaning we can’t use it on a ptr-ptr like we expected - to fix that we would need to relax `Memorable (Ptr a)` to `Memorable (Ptr (Ptr a))` which I might in the near future.\n\nSo, how well does this work? Let’s define our instance for `RNG` - it should be reasonably similar to how difficult it will be for other Botan objects, so this will give idea of how hard this will be to apply to the rest of the library:\n\n\n    instance BotanObject RNG where\n\n        type BotanStruct RNG = BotanRNGStruct\n        type BotanPtr    RNG = BotanRNG\n\n        toBotanPtr ptr  = MkBotanRNG ptr\n        toBotan    fptr = MkRNG      fptr\n        botanFinalizer  = botan_rng_destroy\n\n\nAnd that’s it! RNG no longer needs `mkBindings`, this does all of the same work.\n\n* * *\n\nNext up, we’ll be dealing with the helper methods, such as `mkCreateObjectCString` in:\n\n\n    rngInit :: RNGType -> IO RNG\n    rngInit = mkCreateObjectCString createBotan botan_rng_init\n\n\nNominally, there is nothing wrong with this, aside from `mkCreateObjectCString` being a part of `mkBindings`, and thus, is no longer imported. However, it is just a thin wrapper around `createBotan` that calls `withMem` over a bytestring before passing it as an additional argument - very reader / profunctor-ish, possibly unnecessary or kept after refactoring to also use the `Memory` classes.\n\nWe will get to that next time, as we continue finishing our refactor of the `RNG` module.",
  "title": "Botan bindings devlog"
}