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.")