Importing a C function as throwing?

I'd like to write a C function (not an objc method) that is imported into swift as a throwing function. I found the swift_error clang attribute, which looks promising, but I couldn't get it to work. Is this attribute currently supported? I tried the following function declaration:

void TestFunctionNonnullError(NSError * _Nullable * _Nullable error) __attribute__((__swift_error__(nonnull_error)));

Which unfortunately gets imported into swift as:

public func TestFunctionNonnullError(_ error: NSErrorPointer)

What I would like to see is this:

public func TestFunctionNonnullError() throws

I also tried using a CFErrorRef * argument, but that also was not imported as a throwing function.

The swift_error clang attribute has a few other options, and I tried them all without success. I also searched around in various Foundation and CoreFoundation headers for a working example, but couldn't find anything.

3 Likes

Someone with deeper knowledge may correct me, but C functions don't throw Swift Expections, or any other kind, to my knowledge. They'd have to know the Swift Error type, which they do not.

You will want to wrap it in some Swift that will throw if an error is detected.

2 Likes

This could have worked in principle:

#include <Foundation/Foundation.h>
BOOL foobar(int param, NSError** error);

However I don't think this was ever supported.

The docs only talk about using swift_error in the context of Objective-C so I'm not sure whether it's possible to use it on a C function. Though afaik it's spelled swift_error not __swift_error__, so perhaps try again with that correction. Crucially CoreFoundation APIs return errors as retained (+1) whereas NSError pointers are usually autoreleased (+0), so if the annotation does work you might need some ARC annotations to make things work correctly.

If all else fails you can always use CF_REFINED_FOR_SWIFT i.e. __attribute__((swift_private)) instead, which should import your function into Swift as __TestFunctionNonnullError and hide it from documentation/autocomplete. You could then add a Swift overlay to your module and wrap the function there. Again, be wary of ownership semantics when reading or writing the error.

That is interesting, because the Clang documentation says about that attribute

The swift_error attribute controls whether a particular function (or Objective-C method) is imported into Swift as a throwing function, and if so, which dynamic convention it uses.

2 Likes

I decided to try and get this to do something, as an exercise in becoming less ignorant of this aspect of C/Swift relations.

Here's my code:

int  tossit(int x, CFErrorRef *error) __attribute__((__swift_error__(nonzero_result))) {
    if (x > 256) {
        CFErrorRef err = CFErrorCreate(NULL, kCFErrorDomainPOSIX, 1, NULL);
        error = &err;
        return -1;
    }
    return x;
}

The header just reflect the above declaration, and includes CFError.h

Alas, my call to it:

var y: Int32 = 0
do {
    y = tossit(512, nil)
    print(y)
} catch {
    print(error)
}

yields only "'catch' block is unreachable because no errors are thrown in 'do' block"

I think I am more confused than before.

I dunno, wrapping it feels saner to me, but then, I last spent serious time writing C in the 90s.

My wrap job, on the off chance anyone cares:

int tossitSwiftly(int x) {
    if (x > 256) {
        return -1;
    }
    return x;
}

enum MyError: Error {
    case TooHighError
}

func toss_it(_ x: Int32) throws {
    let res = tossitSwiftly(x)
    if res < 0 {
        throw MyError.TooHighError
    }
}

try toss_it(512)