On 5/02/2025 1:38, Tomas Volf wrote:
Hello,

I would like to dispatch exceptions based on errno, in particular to
return #f on ENOENT, and re-raise the exception otherwise.  My current
(working as far as I can tell) solution is:

   (with-exception-handler
       (λ (exc)
         (and (not (= ENOENT (car (list-ref (exception-args exc) 3))))
              (raise-exception exc)))
     (λ ()
       (with-input-from-file (path realm query) read))
     #:unwind? #t
     #:unwind-for-type 'system-error)

This doesn't answer your question, but do note that this doesn't only catch exceptions of 'with-input-from-file', but also from 'path' and 'read'.I see no reason for 'read' to produce 'ENOENT', but 'path' conceivably might (e.g. if it uses information recorded in a bunch of files, and some required info/file hasn't been read into cache yet).

Exceptions definitely do have their uses, but if you expect an exception to happen right after it is produced, then exceptions tend to get in your way and it would be more convenient if it was just a regular return value in some way (e.g. 'Either a Errno' in Haskell).

For this, I'd recommend defining a general procedure and general macro

;; (maybe add optional 'filter' argument to only consider certain errno, ;; and let other errno through as an exception?) (define (%handle-system-error thunk success error) ;; do (thunk), in case of a system-error, do (error errno) as a tail-call, on success ;; do (apply success [return values of thunk]) ;; let other exceptions pass-through [...])

;; (handle-system-error (some-syscall-like-thing bla bla) ((result result2) [do stuff]) (errno [do other stuff])

(define-syntax handle-system-error [...])

and defining your own variant of 'with-input-file' that uses 'handle-system-error'

Given how common such situations are, and how convenient being able to treat exceptions as non-exceptional situations occasionally can be, I think something like 'handle-system-error' would be good to have in Guile itself.

However, I do not consider it very elegant, especially this part:

--8<---------------cut here---------------start------------->8---
(car (list-ref (exception-args exc) 3))
--8<---------------cut here---------------end--------------->8---

Is there better way to do this?

I recommend 'guard'. It let you catch exceptions as condition objects (well, objects in general since _technically_  you can raise other objects as well). And, you can do some filtering (more general than only on type), so instead of re-raising other exceptions, you can simply not catch them. It is also more straightforward to access 'fields' of condition objects, since they are record types instead of list structures.

For that part in particular, condition objects (which for no mentioned reason have been renamed to exception objects, and for no mentioned reason some condition types have been renamed, even though SRFI and RnRS conditions predate (ice-9 exceptions)) are convenient - if, hypothetically, a &system-error exception existed, then you could extract the errno with a hypothetical 'system-error-errno' procedure.

It doesn't seem like 'system-error' has been converted to a condition type yet, so you would need to add this to Guile first. (There is some machinery for automatic conversion between 'throw/catch' exception lists and condition objects - the former are implemented in terms of the latter IIRC.)

Second question is how to print exceptions including a stack trace.  I
noticed there is display-error, however it takes a frame argument and I
am not sure where to get it.  I did not found any exception-frame
procedure.  However since the default exception-handle *is* able to
print the stack on exception, I assume it is possible.  Any tips?

There is no 'exception-frame', since frames aren't part of condition objects. Frames are also (unless that has been reimplemented) rather ephemeral - they are essentially represented as stack pointers, and after unwinding those aren't valid anymore.

That said, this could have been implemented differently with a spaghetti stack, where I think duplicating a stack region can be made almost zero cost (*)(***). So then stacks _could_ perhaps be cheaply added to condition objects, though if the condition object is kept around for a while you would need an option to remove the stack from the condition, to allow GC to collect it.)

How to print stack: do #:unwind? #false and put (backtrace) or similar in the exception handler. Note that stack copies aren't recorded in the exception object, so you need to do #:unwind? #t if you want the location where the exception was raised, instead of the location of 'with-exception-handler'.

(*) not including the cost of having a spaghetti stack in the first place - I don't know the cost of that. (**) thinking about it, I'd expect delimited continuations to do similar things already - I mean, delimited continuations seem to work nicely for Guile-Fibers. Maybe you could just capture a continuation in the exception handler, and afterwards inspect the continuation? Maybe even _today_? (***) (***) If it's sufficiently efficient, perhaps Guile could automatically record the stack in the condition (with some option to enable/disable this, for GC reasons).

Best regards, Maxime Devos

Reply via email to