#Error handling without exceptions

1 messages · Page 1 of 1 (latest)

noble epoch
#

I'm wondering about the most idiomatic way to deal with error states without using exceptions. Yes, I'm using betterC, and no, I don't have a choice since I'm writing a drop-in replacement for an existing C library.

That library really doesn't have any error reporting except for log output, but I still need to deal with parsing, etc. errors internally.

pulsar ether
#

In a statically-typed setting like this sumtypes might be a good option

late aspen
#

no, I don't have a choice since I'm writing a drop-in replacement for an existing C library.

standard D can do that easily. it is a complete myth that betterC is needed for this

of course a C api can't throw exceptions.... so you will want to do your own C compatible struct

noble epoch
#

The original version of the library is distributed in both shared/static library form

late aspen
#

you do have to call the init function but that's common among all kinds of libs (see for example SDL_init())

noble epoch
#

I'm supposing it's possible to have something like:

extern (C) void doSomethingWithLibrary() {
  initRuntime();
  // continue as normal
}
#

I suppose the init function probably wants to be called only once though

#

so I'd probably store "ready" state in a global variable

late aspen
#

it can be called as many times as you want, it refcounts its init/term calls

frigid marsh
#

rt_init and rt_term must be called an equal number of times.

#

You can also call Runtime.initialize

late aspen
#

but i would recommend not actually init/term over and over again; keep the refcount >= 1 for the duration of your lib's use; the lib user calling the init/term functions is recommended (and again remember how common this is among C libs already!)

noble epoch
#

sadly, I can't make them do that

#

the library I'm replacing is really quite awful (which is why I'm rewriting this), so whatever their API is, I have to match it

#

which means I can't expect explicit initialization

frigid marsh
#

If you don't have to care about de-initialization, you can just use a static variable to ensure it's initialized.

noble epoch
#

I'm assuming nothing bad happens without deinitialization, except for uncalled destructors

late aspen
#

yeah

#

there's some memory that won't be freed too but nothing catasrophic

#

and of course when the program closes that's all cleaned up

frigid marsh
#

Any static destructors will not be called (which means the GC will never be terminated). So if you did something like this, it wouldn't happen: d shared static ~this() { deleteLockFile(); }

noble epoch
#

thankfully the library doesn't do any I/O

frigid marsh
#

Since you know what the situation is, you can avoid doing that kind of stuff. The standard library itself is fine to leave unterminated.

#

betterC is also an option. But is more painful.

noble epoch
#

I'll avoid betterC as much as I can

#

this should make the rest of my code more sane too, since I intend to expose a separate, better API alongside the old one

late aspen
#

tho for a C api you still prolly want to do error as a returned struct or a pointer to error code arg or something like that

#

so the C users can easily catch it

noble epoch
#

ahahaha, you see, that's the thing
the library itself doesn't return any errors (except for NULL/0 when a select few functions fail)

#

in fact, it has almost no error handling at all

#

so the errors will never cross the C boundary

late aspen
#

ok that simplifies lol

noble epoch
#

oh, yeah, how does the GC deal with pointers being put in malloc'd memory?

late aspen
#

you have to tell it about them if it i needs to track it, see GC.addRange

noble epoch
#

that won't backfire if the library user calls free() later, right?

late aspen
#

i'd prolly recommend generally not doing that; keep the malloc stuff all malloc'd and keep the GC stuff in a separate arena

#

user calling free might break that cuz it won't gc.removerange

noble epoch
#

the original library doesn't even do allocation, so I'm kind of playing with fire

late aspen
#

yeah allocating in C libs is always painful. C is so bad lol

#

i g2g ttyl

noble epoch
#

hrm, is this a good way to keep track of runtime initialization?

private struct RuntimeState {
    static bool initialized = false;

    static this() {
        RuntimeState.initialized = true;
    }
}
late aspen
#

i think it'd work but i'd rather just keep your bool set by your init function

frigid marsh
#

Yeah, I'd use the init function to do it. There are 2 problems with this mechanism: 1. it should be shared static this, or the function will run on all new threads. I'd also make the boolean __gshared instead of static 2. this introduces some connections in the module graph, so cycles may start happening.

noble epoch
#

I'll move the logic to my init function then, to prevent future problems

noble epoch
#
private __gshared bool initialized = false;

export void initDRuntimeOnce() {
    if (initialized) { return; }
    initialized = Runtime.initialize();
    if (!initialized) {
        internalPanic("failed to initialize D runtime");
    }
}