gnu-arch-users
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

GError (Re: [Gnu-arch-users] "librifying" libarch)


From: Tom Lord
Subject: GError (Re: [Gnu-arch-users] "librifying" libarch)
Date: Mon, 20 Oct 2003 13:58:32 -0700 (PDT)


    > From: Colin Walters <address@hidden>

This is a "coding style" issue, not something where there's absolute
perfect arguments that objectively prove one approach is better than
another.   

You suggested that if anyone works on librifying libarch that they
adopt a GError-like approach.   Stylistically, in arch, ick!

I'll explain my objection and suggest a close alternative:


    > On Mon, 2003-10-20 at 12:55, Tom Lord wrote:
    > >    Leave the invariants alone, but otherwise, if any of the -utils can
    > >    exit (by panic, exit, or a safe_* call), change them to return an
    > >    error value and update callers.

    > GLib has a nice thing called GError that is a good way to write things
    > like this.  The GError structure itself is actually quite simple:

    > struct _GError
    > {
    >   GQuark       domain;
    >   gint         code;
    >   gchar       *message;
    > };

That's about two fields too many.

    > domain and code just give a programmatic way to know which layer raised
    > an error; in OO languages this would be equivalent to the class of the
    > exception.

I don't know what you mean by this "which layer" stuff.   The GError
documentation talks about domain indicating a "module" and I don't
know that they mean either.

I'm horrified to think that if I have a layered software system with
layers:


        A       |
        B       | calls
        C       V


that layer A can get back an error that is programmatically labeled as
coming from layer C and try to handle it on that basis.   That's an
abstraction violation:  A isn't supposed to know if or how B uses C.
So, `domain' is just a pit of hell, here.

GError is a sort-of-ok debugging aid, but there are plenty of
altnerative ways to debug things that don't require ->domain and
->code.  I'm scared to think of code that uses the domain/code stuff
for anything other than debugging and not horribly impressed by
systems which use them even for debugging.

Let's look at one aspect of the traditional unix approach to error
values: errno.

There's three aspects to errno:

        a) there's a global lvalue called errno
        b) there's a set of #defines of possible errno 
           values as a unique integer (e.g.: EEXISTS)
        c) there's a function that takes an errno value
           and gives you back a "standard string" describing 
           the error

That's almost perfect except that it sucks.   To fix it, we need to
fix two out of the three aspects:


        a) global value?!??  What, are you kidding?

           GError gets right the idea that the error-type
           return should be an output parameter, not a 
           global lvalue.   I like that part of GError:
           functions that need to distinguish among the
           reasons they are returning an error should
           accept a parameter of type `arch_error_t *'.

           (In defense of the unix implementors, they thought
           up errno in a context where all processes were
           expected to be single threaded.   It's not a perfect
           defense, but it's a pretty good one.)

           GError _also_ gets right the idea that its often
           handy to have a function return an indication that
           an error occured separately from returning the 
           "error code".    Can't _always_ work out nicely but
           it can so often that its handy.   Return -1 or NULL
           in addition to setting *error_return.

           (Look at how the vu_ functions in libhackerlab take
           an errno parameter like this.)


        b) #defines aren't extensible

           Only one authority gets to write <errno.h>.

           Another thing that sucks about the errno mechanism
           is that if I want to return some error, but none
           of the EFOO values characterize that error, I'm SOL
           (perhaps the errno values should include ESOL).

           We need some coherent way for seperately developed
           modules to all define their own new error codes
           and the unix errno mechanism doesn't give us that.

           (Reading section 3 of a unix manual, you can see the 
            errno idea just trail off.   We have to retrospectively
            interpret it as primarily just an (awkward) mechanism
            for getting error codes back from system calls.)

           _That_ problem, the lack of extensibility, is, I strongly
           suspect, is the original idea of the `domain' part of a
           GError.  Anyone can write some code that, at run time,
           allocates a new domain, and then fills in `code' with a
           staticly allocated integer.  I've got my set of
           errno-like-integers, you've got yours, it's the `domain'
           that sets them apart.  The problem, though, is that the
           GError designers are suffering from a myth: that error
           codes have to be integers.

           Heck, there's a much simpler solution that's just 
           as good as domain+code:

                typedef char * arch_error_t;

            Now, if I want to make a new error code, I can just
            declare:

                extern arch_error_t my_err_machine_caught_fire;

            and define:

                generic_error_t my_err_machine_caught_fire = 
                                ("The machine caught fire");


            Note that that also gives us a trivial version of the
            traditonal `strerr' function and plays nicely with i18n.

            These are just about as good as errno codes.  Whereas with
            errno codes I can write:

                if (errno == EEXISTS)

            now I write:

                if (err == my_err_machine_caught_fire)

            The only property lost here is that error codes aren't
            guaranteed to be small integers (a property also semi-lost
            in the GError approach).

How does this approach compare to GError?  I think it involves a lot
less baggage.  To pick on a few key GError functions and show how they
work out in my approach:


        g_error_matches(&err, domain, code)

becomes:

        (err == my_err_machine_caught_fire)



The call:

        g_set_error (err_pointer, domain, code)

becomes

        if (err_pointer)
          *err_pointer = my_err_machine_caught_fire;


The call:

        g_propogate_error (dest_errptr, src_err)

becomes in the worst case:

        if (dest_errptr)
          *dest_errptr = src_err;

but more often instead of

        sub_function (&src_err, ....);
        if (src_err)
          g_propogate_error (dest_errptr, src_err);

i'll just use:

        sub_function (dest_errptr, ....);

Finally:

        g_clear_error (dest_errptr);

is just:

        if (dest_errptr)
          *dest_errptr = 0;


One Of These Days I should just add a 

        typedef char * error_t;

to libhackerlab.

-t






reply via email to

[Prev in Thread] Current Thread [Next in Thread]