Protocol function declaration with dynamic signature

I'm working on a Path library based around C APIs.

In the open(2) man page, the open function has either of the following signatures:

int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);

In the opendir(3) man page, then opendir function has the following signature:

int opendir(const char *name);

I have a protocol Openable for types that can be opened:

public protocol Openable: Path {
    var fileDescriptor: Int32 { get }
    func close() throws
    func open(/* what do I put here */) throws -> Open<Self>
}

I want to require some kind of open function in the Openable protocol, but I'm not sure what I should use for the function signature. The C open directory function (same on Mac and Linux) does not require anything other than the path name, but the C open file function requires 2-3 arguments. Is there some way to make a dynamic function signature in protocols?

I did something like this (not exactly, but close enough):

public protocol Openable: Path {
    associatedtype OpenOptions
    var fileDescriptor: Int32 { get }
    func close() throws
    func open(_ options: OpenOptions) throws -> Open<Self>
}

extension FilePath: Openable {
    public typealias OpenOptions = (options: Int, mode: FileMode?) // mode is only needed when creating paths

    public func open(_ options: OpenOptions) throws -> Open<FilePath> {
        // Implementation using `open(2)` here...
    }

    public func open(permissions: OpenFilePermissions, flags: OpenFileFlags = [], mode: FileMode? = nil) throws -> Open<FilePath> {
        return try open((options: permissions.rawValue | flags.rawValue, mode: mode))
    }
}

extension DirectoryPath: Openable {
    public typealias OpenOptions = ()

    public func open(_ options: () = ()) throws -> Open<DirectoryPath> {
        // Implementation using `opendir(3)` here...
    }
}

Which worked, but I'd really just rather have the entire signature be dynamic rather than just using a single parameter that's a unique tuple based on the PathType. I don't want to just remove the need for the open function from the protocol because then it's a hidden implementation detail that your type needs to include some way to open the path and I prefer to be explicit in my protocol declaration.

Basically, doing it this way satisfies the protocol requirement, but the protocol function will probably only ever be called indirectly on FilePath and I prefer not to have (public) functions that I know won't ever be used directly.

I also tried this:

public protocol Openable: Path {
    associatedtype OpenOptions
    var openFunc: (OpenOptions) throws -> Open<Self> { get }
}

extension FilePath: Openable {
    public typealias OpenOptions = (permissions: OpenFilePermissions, flags: OpenFileFlags, mode: FileMode?)

    public var openFunc: (OpenOptions) throws -> Open<Self> { return self.openFile }

    public func open(permissions: OpenFilePermissions, flags: OpenFileFlags = [], mode: FileMode? = nil) throws -> Open<FilePath> {
        // Implementation using `open(2)` here...
    }

But it fails to compile since the signature of openFile doesn't match what openFunc expects. If I remove the OpenOptions' tuple labels then I get closer to the expected type.

Is there any way to have dynamic function signatures? Or possibly to pass a tuple as a functions arguments?

Why can't Openable require a parameter-less open() and the options would have to be held in the state of the conforming type?

That is something I haven't considered and may try out. There are instances where I need the options used to open the file anyways and just having them stored as a property of the FilePath might not be a bad idea.

should open even be declared as a protocol requirement? how would you open a generic type without knowing which of the three cases it is? Do you just ignore the extra parameters? in which case, just declare all three parameters in the open requirement.

2 Likes