From abdda7262a38d3f40f64bded846feabd521f4f61 Mon Sep 17 00:00:00 2001
From: "carpentry-heartbeat[bot]"
Date: Sun, 24 May 2026 09:19:57 +0200
Subject: [PATCH 1/3] Return Result with dlerror messages instead of Maybe
open, get, and get-from-module now return (Result a String) where the
error branch carries the actual OS error from dlerror(). This removes
the need for callers to wrap results in Maybe.to-result with a generic
message.
Also fixes a latent double-free: dlerror() returns a pointer to a
static buffer owned by the implementation, but was registered as
returning String (heap-owned). Now registered as (Ptr CChar) with a
copying wrapper via String.from-cstr.
The rebind macro is updated to match on Result, and now prints the
real dlerror message instead of a generic "Failed to find symbol".
Breaking change: callers using Maybe.Just/Maybe.Nothing pattern
matching on these functions must switch to Result.Success/Result.Error.
---
dynlib.carp | 33 +++++++++++++++++------------
examples/c_and_carp/example.carp | 3 +--
examples/carp_and_carp/example.carp | 4 +---
3 files changed, 21 insertions(+), 19 deletions(-)
diff --git a/dynlib.carp b/dynlib.carp
index e10c715..f037715 100644
--- a/dynlib.carp
+++ b/dynlib.carp
@@ -5,15 +5,13 @@
strives to make code loading and calling dynamically easy.
```
-(load \"https://github.com/carpentry-org/dynlib@0.0.4\")
+(load \"https://github.com/carpentry-org/dynlib@0.0.5\")
(defn main []
(println*
&(=> (DynLib.open \"libt.so\")
- (Maybe.to-result @\"Couldn’t open libt.so\")
(Result.and-then
- (fn [lib] (Maybe.to-result (DynLib.get lib \"inc\")
- @\"Couldn’t load symbol inc\")))
+ (fn [lib] (DynLib.get lib \"inc\")))
(Result.map (fn [f] (Int.str (f 1)))))))
```")
(defmodule DynLib
@@ -32,28 +30,35 @@ strives to make code loading and calling dynamically easy.
(register dlsym (Fn [Lib (Ptr CChar)] (Ref a)) "DynLib_dlsym")
(hidden dlclose)
(register dlclose (Fn [Lib] Int) "dlclose")
+ (private dlerror-)
+ (hidden dlerror-)
+ (register dlerror- (Fn [] (Ptr CChar)) "dlerror")
+ (private dlerror)
(hidden dlerror)
- (register dlerror (Fn [] String) "dlerror")
+ (defn dlerror [] (String.from-cstr (dlerror-)))
(hidden valid?)
(register valid? (Fn [a] Bool) "DynLib_isvalid")
- (doc open "opens a shared library `lib`.")
+ (doc open "opens a shared library `lib`. Returns a `Result` with the library
+handle on success, or the `dlerror` message on failure.")
(defn open [lib]
(let [l (dlopen (cstr lib) lazy)]
- (if (valid? l) (Maybe.Just l) (Maybe.Nothing))))
+ (if (valid? l) (Result.Success l) (Result.Error (dlerror)))))
- (doc get "gets a function named `fname` from a shared library `lib`.")
+ (doc get "gets a function named `fname` from a shared library `lib`. Returns
+a `Result` with the function on success, or the `dlerror` message on failure.")
(defn get [lib fname]
(let [f (dlsym lib (cstr fname))]
- (if (valid? f) (Maybe.Just @f) (Maybe.Nothing))))
+ (if (valid? f) (Result.Success @f) (Result.Error (dlerror)))))
(doc get-from-module
"gets a function named `fname` from a Carp module `md` from inside
-a shared library `lib`.")
+a shared library `lib`. Returns a `Result` with the function on success, or the
+`dlerror` message on failure.")
(defn get-from-module [lib md fname]
(let [fqn (fmt "%s_%s" &(Pattern.substitute #"\." md "_" -1) fname)
f (dlsym lib (cstr &fqn))]
- (if (valid? f) (Maybe.Just @f) (Maybe.Nothing))))
+ (if (valid? f) (Result.Success @f) (Result.Error (dlerror)))))
(doc close "closes a library `lib`. Either returns nothing or, if an error
occurs, it returns the error message.")
@@ -63,7 +68,7 @@ occurs, it returns the error message.")
(defmacro rebind [lib s]
(list 'match
(list 'DynLib.get lib (str s))
- '(Maybe.Nothing)
- (list 'IO.errorln (String.join ["Failed to find symbol " (str s)]))
- '(Maybe.Just s)
+ '(Result.Error e)
+ (list 'IO.errorln 'e)
+ '(Result.Success s)
(list set! s 's))))
diff --git a/examples/c_and_carp/example.carp b/examples/c_and_carp/example.carp
index f7d0555..7387604 100644
--- a/examples/c_and_carp/example.carp
+++ b/examples/c_and_carp/example.carp
@@ -3,6 +3,5 @@
(defn main []
(println*
&(=> (DynLib.open "libt.so")
- (Maybe.to-result @"Couldn’t open libt.so")
- (Result.and-then &(fn [lib] (Maybe.to-result (DynLib.get lib "add") @"Couldn’t load symbol")))
+ (Result.and-then &(fn [lib] (DynLib.get lib "add")))
(Result.map &(fn [f] (Int.str (f 1 2)))))))
diff --git a/examples/carp_and_carp/example.carp b/examples/carp_and_carp/example.carp
index 8c8d13b..2469885 100644
--- a/examples/carp_and_carp/example.carp
+++ b/examples/carp_and_carp/example.carp
@@ -3,8 +3,6 @@
(defn main []
(println*
&(=> (DynLib.open "libt.so")
- (Maybe.to-result @"Couldn’t open libt.so")
(Result.and-then
- &(fn [lib]
- (Maybe.to-result (DynLib.get-from-module lib "Shared" "add") @"Couldn’t load symbol")))
+ &(fn [lib] (DynLib.get-from-module lib "Shared" "add")))
(Result.map &(fn [f] (Int.str (f 1 2)))))))
From f862fb0196f150085e311f20c2e74206e09ba46b Mon Sep 17 00:00:00 2001
From: "carpentry-heartbeat[bot]"
Date: Sun, 24 May 2026 16:41:08 +0200
Subject: [PATCH 2/3] Guard against NULL dlerror(), update README and docs for
Result API
---
README.md | 19 ++++++++++---------
docs/DynLib.html | 21 +++++++++++----------
dynlib.carp | 4 +++-
3 files changed, 24 insertions(+), 20 deletions(-)
diff --git a/README.md b/README.md
index 65efb9b..011a51f 100644
--- a/README.md
+++ b/README.md
@@ -10,27 +10,28 @@ Provided there is a function `inc` that increments a number in a library
follows:
```clojure
-(load "https://github.com/carpentry-org/dynlib@0.0.4")
+(load "https://github.com/carpentry-org/dynlib@0.0.5")
(defn main []
(println*
&(=> (DynLib.open "libt.so")
- (Maybe.to-result @"Couldn’t open libt.so")
(Result.and-then
- (fn [lib] (Maybe.to-result (DynLib.get lib "inc") @"Couldn’t load symbol inc")))
+ (fn [lib] (DynLib.get lib "inc")))
(Result.map (fn [f] (Int.str (f 1)))))))
```
-We’re using `Result` here to get informative error messages, but this is all
-optional. If you want to throw safety out of the window, something like this
-could also work—though I wholeheartedly advise against it:
+`open` and `get` return `(Result a String)`, so the error branch already
+carries the real `dlerror()` message—no need to wrap in `Maybe.to-result`.
+
+If you want to throw safety out of the window, something like this could also
+work—though I wholeheartedly advise against it:
```clojure
-(load "https://github.com/carpentry-org/dynlib@0.0.3")
+(load "https://github.com/carpentry-org/dynlib@0.0.5")
(defn main []
- (let [lib (Maybe.unsafe-from (DynLib.open "libt.so"))
- f (Maybe.unsafe-from (DynLib.get lib "inc"))]
+ (let [lib (Result.unsafe-from-success (DynLib.open "libt.so"))
+ f (Result.unsafe-from-success (DynLib.get lib "inc"))]
(println* &(Int.str (f 1)))))
```
diff --git a/docs/DynLib.html b/docs/DynLib.html
index b2b0924..84c5a41 100644
--- a/docs/DynLib.html
+++ b/docs/DynLib.html
@@ -31,15 +31,13 @@
DynLib is a module for loading shared libraries at runtime. It
strives to make code loading and calling dynamically easy.
-
(load "https://github.com/carpentry-org/dynlib@0.0.4")
+(load "https://github.com/carpentry-org/dynlib@0.0.5")
(defn main []
(println*
&(=> (DynLib.open "libt.so")
- (Maybe.to-result @"Couldn’t open libt.so")
(Result.and-then
- (fn [lib] (Maybe.to-result (DynLib.get lib "inc")
- @"Couldn’t load symbol inc")))
+ (fn [lib] (DynLib.get lib "inc")))
(Result.map (fn [f] (Int.str (f 1)))))))
@@ -75,13 +73,14 @@
defn
- (Fn [Lib, (Ref String a)] (Maybe b))
+ (Fn [Lib, (Ref String a)] (Result b String))
(get lib fname)
-
gets a function named fname from a shared library lib.
+ gets a function named fname from a shared library lib. Returns
+a Result with the function on success, or the dlerror message on failure.
@@ -95,14 +94,15 @@
defn
- (Fn [Lib, (Ref String a), b] (Maybe c))
+ (Fn [Lib, (Ref String a), b] (Result c String))
(get-from-module lib md fname)
gets a function named fname from a Carp module md from inside
-a shared library lib.
+a shared library lib. Returns a Result with the function on success, or the
+dlerror message on failure.
@@ -116,13 +116,14 @@
defn
- (Fn [(Ref String a)] (Maybe Lib))
+ (Fn [(Ref String a)] (Result Lib String))
(open lib)
-
opens a shared library lib.
+ opens a shared library lib. Returns a Result with the library
+handle on success, or the dlerror message on failure.
diff --git a/dynlib.carp b/dynlib.carp
index f037715..18ab866 100644
--- a/dynlib.carp
+++ b/dynlib.carp
@@ -35,7 +35,9 @@ strives to make code loading and calling dynamically easy.
(register dlerror- (Fn [] (Ptr CChar)) "dlerror")
(private dlerror)
(hidden dlerror)
- (defn dlerror [] (String.from-cstr (dlerror-)))
+ (defn dlerror []
+ (let [err (dlerror-)]
+ (if (valid? err) (String.from-cstr err) @"unknown error")))
(hidden valid?)
(register valid? (Fn [a] Bool) "DynLib_isvalid")
From 7882b11ce1eda8b133fed6c23d9d94a190f3ed81 Mon Sep 17 00:00:00 2001
From: "carpentry-heartbeat[bot]"
Date: Mon, 25 May 2026 07:08:56 +0200
Subject: [PATCH 3/3] Fix definition ordering: move register valid? above defn
dlerror
---
dynlib.carp | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/dynlib.carp b/dynlib.carp
index 18ab866..24b9abf 100644
--- a/dynlib.carp
+++ b/dynlib.carp
@@ -33,13 +33,13 @@ strives to make code loading and calling dynamically easy.
(private dlerror-)
(hidden dlerror-)
(register dlerror- (Fn [] (Ptr CChar)) "dlerror")
+ (hidden valid?)
+ (register valid? (Fn [a] Bool) "DynLib_isvalid")
(private dlerror)
(hidden dlerror)
(defn dlerror []
(let [err (dlerror-)]
(if (valid? err) (String.from-cstr err) @"unknown error")))
- (hidden valid?)
- (register valid? (Fn [a] Bool) "DynLib_isvalid")
(doc open "opens a shared library `lib`. Returns a `Result` with the library
handle on success, or the `dlerror` message on failure.")