Importing Empty C Structs


(Jeff Kelley) #1

I’m trying to use the Swift Package Manager to import libgit2 and am running into some issues. I have libgit2 installed via Homebrew and have configured the module map thusly:

module CGit2 [system] {
    header "/usr/local/include/git2.h"
    link "git2"
    export *
}

I can then use this with the SPM to initialize the Git system:

import CGit2

let result = git_libgit2_init()

defer { git_libgit2_shutdown() }

print("Result: \(result)")

This all works fine. When I start to try to use it—for instance, opening a repository:

var repo: git_repository = nil

let error = git_repository_open(&repo, "/Users/jeff/Projects/CGit2")

guard let repo = repo else { print("No repo!"); exit(error) }

I get an error: “use of undeclared type ‘git_repository’”

Here’s how git_repository is declared in /usr/local/include/git2/types.h:

typedef struct git_repository git_repository;

An empty struct. Interesting. A quick sample project in Xcode shows that this line will not generate anything in the Swift generated interface. So it appears to me that this type is invisible to Swift.

Question: Is there a way to use libraries like this in Swift? Should I file a bug that typedef’d structs like this are not imported into the generated interface? Thanks in advance for any pointers!

Jeff Kelley

SlaunchaMan@gmail.com | @SlaunchaMan <https://twitter.com/SlaunchaMan> | jeffkelley.org <http://jeffkelley.org/>


(Joe Groff) #2

If a struct is incomplete in C, it can only be manipulated via pointers. We don't import `git_repository`, but `git_repository*` should be imported as a typealias for COpaquePointer. We have plans to eventually improve the type safety here by importing the type, though even then, you would not be able to construct or pass around values of the incomplete type, only pointers.

-Joe

···

On Feb 11, 2016, at 9:41 PM, Jeff Kelley via swift-users <swift-users@swift.org> wrote:

I’m trying to use the Swift Package Manager to import libgit2 and am running into some issues. I have libgit2 installed via Homebrew and have configured the module map thusly:

module CGit2 [system] {
    header "/usr/local/include/git2.h"
    link "git2"
    export *
}

I can then use this with the SPM to initialize the Git system:

import CGit2

let result = git_libgit2_init()

defer { git_libgit2_shutdown() }

print("Result: \(result)")

This all works fine. When I start to try to use it—for instance, opening a repository:

var repo: git_repository = nil

let error = git_repository_open(&repo, "/Users/jeff/Projects/CGit2")

guard let repo = repo else { print("No repo!"); exit(error) }

I get an error: “use of undeclared type ‘git_repository’”

Here’s how git_repository is declared in /usr/local/include/git2/types.h:

typedef struct git_repository git_repository;

An empty struct. Interesting. A quick sample project in Xcode shows that this line will not generate anything in the Swift generated interface. So it appears to me that this type is invisible to Swift.

Question: Is there a way to use libraries like this in Swift? Should I file a bug that typedef’d structs like this are not imported into the generated interface? Thanks in advance for any pointers!


(Jeremy Pereira) #3

typedef struct git_repository git_repository;

An empty struct. Interesting. A quick sample project in Xcode shows that this line will not generate anything in the Swift generated interface. So it appears to me that this type is invisible to Swift.

This is not an empty struct, it is an example of an incomplete type.

https://en.wikipedia.org/wiki/C_syntax#Incomplete_types

It is a feature of C that you can specify that a struct type exists without giving any information about its internal structure or even size. Typically, a struct definition like the above is placed in a header file and it is redefined in a .c file with all of its internal structure. Compilation units other than the .c file in which the struct is fully defined have know knowledge of what the struct looks like or even how big it is, so all they can do is store pointers to such structs and pass them as parameters and return values.

Any code that manipulates the internals of the struct has to be defined in the implementation file that the struct is fully defined in. Other compilation units can only manipulate the struct through functions that form the API, usually defined in the same header as the incomplete type.

This is a very common pattern in C because it provides complete encapsulation. In fact, it provides better encapsulation than C++ classes.

···

On 12 Feb 2016, at 06:41, Jeff Kelley via swift-users <swift-users@swift.org> wrote:

Jeff Kelley

SlaunchaMan@gmail.com | @SlaunchaMan | jeffkelley.org

_______________________________________________
swift-users mailing list
swift-users@swift.org
https://lists.swift.org/mailman/listinfo/swift-users


(Jeff Kelley) #4

OK, that makes sense. I printed the dynamic type of git_repository_open() and the first parameter translates from git_repository* to UnsafeMutablePointer<COpaquePointer>. So I can make a typealias from git_repository to COpaquePointer. I’m wondering if I should submit a proposal to swift-evolution to automate that process.

Jeff Kelley

SlaunchaMan@gmail.com <mailto:SlaunchaMan@gmail.com> | @SlaunchaMan <https://twitter.com/SlaunchaMan> | jeffkelley.org <http://jeffkelley.org/>

···

On Feb 12, 2016, at 4:42 AM, Jeremy Pereira <jeremy.j.pereira@googlemail.com <mailto:jeremy.j.pereira@googlemail.com>> wrote:

On 12 Feb 2016, at 06:41, Jeff Kelley via swift-users <swift-users@swift.org <mailto:swift-users@swift.org>> wrote:

typedef struct git_repository git_repository;

An empty struct. Interesting. A quick sample project in Xcode shows that this line will not generate anything in the Swift generated interface. So it appears to me that this type is invisible to Swift.

This is not an empty struct, it is an example of an incomplete type.

https://en.wikipedia.org/wiki/C_syntax#Incomplete_types

It is a feature of C that you can specify that a struct type exists without giving any information about its internal structure or even size. Typically, a struct definition like the above is placed in a header file and it is redefined in a .c file with all of its internal structure. Compilation units other than the .c file in which the struct is fully defined have know knowledge of what the struct looks like or even how big it is, so all they can do is store pointers to such structs and pass them as parameters and return values.

Any code that manipulates the internals of the struct has to be defined in the implementation file that the struct is fully defined in. Other compilation units can only manipulate the struct through functions that form the API, usually defined in the same header as the incomplete type.

This is a very common pattern in C because it provides complete encapsulation. In fact, it provides better encapsulation than C++ classes.

Jeff Kelley

SlaunchaMan@gmail.com | @SlaunchaMan | jeffkelley.org

_______________________________________________
swift-users mailing list
swift-users@swift.org
https://lists.swift.org/mailman/listinfo/swift-users


(Jordan Rose) #5

The main reason we don't do this today is that in the REPL you might import the definition later, and then you can't update the existing type. We definitely need a better story here, though.

Jordan

···

On Feb 12, 2016, at 7:52, Jeff Kelley via swift-users <swift-users@swift.org> wrote:

OK, that makes sense. I printed the dynamic type of git_repository_open() and the first parameter translates from git_repository* to UnsafeMutablePointer<COpaquePointer>. So I can make a typealias from git_repository to COpaquePointer. I’m wondering if I should submit a proposal to swift-evolution to automate that process.

Jeff Kelley

SlaunchaMan@gmail.com <mailto:SlaunchaMan@gmail.com> | @SlaunchaMan <https://twitter.com/SlaunchaMan> | jeffkelley.org <http://jeffkelley.org/>

On Feb 12, 2016, at 4:42 AM, Jeremy Pereira <jeremy.j.pereira@googlemail.com <mailto:jeremy.j.pereira@googlemail.com>> wrote:

On 12 Feb 2016, at 06:41, Jeff Kelley via swift-users <swift-users@swift.org <mailto:swift-users@swift.org>> wrote:

typedef struct git_repository git_repository;

An empty struct. Interesting. A quick sample project in Xcode shows that this line will not generate anything in the Swift generated interface. So it appears to me that this type is invisible to Swift.

This is not an empty struct, it is an example of an incomplete type.

https://en.wikipedia.org/wiki/C_syntax#Incomplete_types

It is a feature of C that you can specify that a struct type exists without giving any information about its internal structure or even size. Typically, a struct definition like the above is placed in a header file and it is redefined in a .c file with all of its internal structure. Compilation units other than the .c file in which the struct is fully defined have know knowledge of what the struct looks like or even how big it is, so all they can do is store pointers to such structs and pass them as parameters and return values.

Any code that manipulates the internals of the struct has to be defined in the implementation file that the struct is fully defined in. Other compilation units can only manipulate the struct through functions that form the API, usually defined in the same header as the incomplete type.

This is a very common pattern in C because it provides complete encapsulation. In fact, it provides better encapsulation than C++ classes.

Jeff Kelley

SlaunchaMan@gmail.com <mailto:SlaunchaMan@gmail.com> | @SlaunchaMan | jeffkelley.org <http://jeffkelley.org/>

_______________________________________________
swift-users mailing list
swift-users@swift.org <mailto:swift-users@swift.org>
https://lists.swift.org/mailman/listinfo/swift-users

_______________________________________________
swift-users mailing list
swift-users@swift.org
https://lists.swift.org/mailman/listinfo/swift-users


(Joe Groff) #6

This could also potentially happen in a standalone executable image, which might have been compiled with the opaque declaration and then dlopen a dylib with the full definition. We'd want Swift's runtime metadata for the C type to converge on the full definition when it's available.

-Joe

···

On Feb 15, 2016, at 10:05 AM, Jordan Rose via swift-users <swift-users@swift.org> wrote:

The main reason we don't do this today is that in the REPL you might import the definition later, and then you can't update the existing type. We definitely need a better story here, though.


(Jordan Rose) #7

Or consider them unique but distinct types that require an explicit conversion (a Worse Is Better answer if we can't come up with a full answer).

Jordan

···

On Feb 15, 2016, at 10:16, Joe Groff <jgroff@apple.com> wrote:

On Feb 15, 2016, at 10:05 AM, Jordan Rose via swift-users <swift-users@swift.org> wrote:

The main reason we don't do this today is that in the REPL you might import the definition later, and then you can't update the existing type. We definitely need a better story here, though.

This could also potentially happen in a standalone executable image, which might have been compiled with the opaque declaration and then dlopen a dylib with the full definition. We'd want Swift's runtime metadata for the C type to converge on the full definition when it's available.