TXR Lisp
TXR&#x27;s FFI can describe a C linked list and convert it in both directions in a call, with correct malloc&#x2F;free memory management.<p>Given this code, which is compiled as part of a larger library called &quot;crazyffi.so&quot;:<p><pre><code>  struct lnode {
    char *datum;
    struct lnode *next;
  };

  void list_update(struct lnode *list)
  {
    struct lnode *iter;
    int i;

    for (i = 0, iter = list; ; iter = iter-&gt;next, i++) {
      char buf[256];
      printf(&quot;lnode[%d]-&gt;datum = %s\n&quot;, i, iter-&gt;datum);

      &#x2F;* Edit every node by adding numeric prefix to the string.
       * We free the old datum, and install a newly malloced
       * string in its place.
       *&#x2F;
      snprintf(buf, sizeof buf, &quot;%d:%s&quot;, i, iter-&gt;datum);
      free(iter-&gt;datum);
      iter-&gt;datum = strdup(buf);

      &#x2F;* When visiting the last node, add one more node.
       *&#x2F;
      if (!iter-&gt;next) {
        snprintf(buf, sizeof buf, &quot;%d:%s&quot;, i + 1, &quot;cow!&quot;);
        iter-&gt;next = malloc(sizeof *iter-&gt;next);
        iter-&gt;next-&gt;datum = strdup(buf);
        iter-&gt;next-&gt;next = 0;
        break;
      }
    }
  }
</code></pre>
This TXR Lisp code calls the function:<p><pre><code>  (typedef lnode (struct lnode
                   (datum str)
                   (next (ptr (struct lnode)))))

  (with-dyn-lib &quot;.&#x2F;crazyffi.so&quot;
    (deffi list-update &quot;list_update&quot; void ((ptr lnode))))

  (let ((ll #S(lnode datum &quot;how&quot;
                     next #S(lnode datum &quot;now&quot;
                                    next #S(lnode datum &quot;brown&quot;
                                                 next nil)))))
    (prinl ll)
    (list-update ll)
    (prinl ll))
</code></pre>
Run it:<p><pre><code>  $ txr linked-test.tl 
  #S(lnode datum &quot;how&quot; next #S(lnode datum &quot;now&quot; next #S(lnode datum &quot;brown&quot; next nil)))
  lnode[0]-&gt;datum = how
  lnode[1]-&gt;datum = now
  lnode[2]-&gt;datum = brown
  #S(lnode datum &quot;0:how&quot; next #S(lnode datum &quot;1:now&quot; next #S(lnode datum &quot;2:brown&quot; next #S(lnode datum &quot;3:cow!&quot; next nil))))
</code></pre>
We see that the list was altered with numeric prefixes on the strings, and a new node was added at the end.<p>Valgrind is completely clean:<p><pre><code>  $ valgrind txr linked-test.tl 
  ==6707== Memcheck, a memory error detector
  ==6707== Copyright (C) 2002-2017, and GNU GPL&#x27;d, by Julian Seward et al.
  [ ... ]
  #S(lnode datum &quot;how&quot; next #S(lnode datum &quot;now&quot; next #S(lnode datum &quot;brown&quot; next nil)))
  lnode[0]-&gt;datum = how
  lnode[1]-&gt;datum = now
  lnode[2]-&gt;datum = brown
  #S(lnode datum &quot;0:how&quot; next #S(lnode datum &quot;1:now&quot; next #S(lnode datum &quot;2:brown&quot; next #S(lnode datum &quot;3:cow!&quot; next nil))))
  ==6707== 
  ==6707== HEAP SUMMARY:
  ==6707==     in use at exit: 2,681,402 bytes in 9,346 blocks
  ==6707==   total heap usage: 14,827 allocs, 5,481 frees, 3,034,573 bytes allocated
  ==6707== 
  ==6707== LEAK SUMMARY:
  ==6707==    definitely lost: 0 bytes in 0 blocks
  ==6707==    indirectly lost: 0 bytes in 0 blocks
  ==6707==      possibly lost: 1,286,596 bytes in 1,533 blocks
  ==6707==    still reachable: 1,394,806 bytes in 7,813 blocks
  ==6707==         suppressed: 0 bytes in 0 blocks
  ==6707== Rerun with --leak-check=full to see details of leaked memory
  ==6707== 
  ==6707== For counts of detected and suppressed errors, rerun with: -v
  ==6707== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
</code></pre>
Now with --free-all:<p><pre><code>  0:sun-go:~&#x2F;txr$ valgrind txr --free-all linked-test.tl 
  ==6721== Memcheck, a memory error detector
  [ ... ]
  #S(lnode datum &quot;how&quot; next #S(lnode datum &quot;now&quot; next #S(lnode datum &quot;brown&quot; next nil)))
  lnode[0]-&gt;datum = how
  lnode[1]-&gt;datum = now
  lnode[2]-&gt;datum = brown
  #S(lnode datum &quot;0:how&quot; next #S(lnode datum &quot;1:now&quot; next #S(lnode datum &quot;2:brown&quot; next #S(lnode datum &quot;3:cow!&quot; next nil))))
  ==6721== 
  ==6721== HEAP SUMMARY:
  ==6721==     in use at exit: 0 bytes in 0 blocks
  ==6721==   total heap usage: 14,830 allocs, 14,830 frees, 3,034,665 bytes allocated
  ==6721== 
  ==6721== All heap blocks were freed -- no leaks are possible
  ==6721== 
  ==6721== For counts of detected and suppressed errors, rerun with: -v
  ==6721== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

</code></pre>
What&#x27;s missing is circularity detection; we cannot pass a graph back and forth in this manner. If we pass a DAG structure, its shared structure will get expanded.<p>Since circularity detection is expensive, that would have to be something enabled by some sort of attribute mechanism.