 Before I get started, if you happen to have a laptop up, there are a bunch of code that go along with these slides, and they are probably going to be more informative than the slides themselves. So if you want to just take a look at them, they're in the bottom right corner as well. So like I was introduced, my name is Liz. Most importantly, I came from Brooklyn, so if I seem kind of tired and out of it, that's because I am very badly jet lagged. I am a queer, trans, polyamorous woman. I live with my family and my very cute dog. If you follow me on Twitter, you won't get anything about Go, but if you want pictures of dogs, I can absolutely provide that. My current employer is VMware, a recent acquirer of a company called Heptio, and let's just get this out of the way. I don't like Go that much, but it seems like y'all don't really like See Go that much, so I think we're going to get along just great. I work on Kubernetes. I work with Sig Cluster Lifecycle and a bunch of projects that won't mean anything very inside Kubernetes baseball, but I do go a lot all day, every day. And this is the most important part of this talk. Why not See Go? I'm going to channel my co-worker Dave Cheney. He has a very good talk. See Go is not Go. Just go ahead and Google it. But to summarize, it's make compilation more complicated, less portable, no cross compilation, more seg faults, no back traces, less tooling. It's a mess. It's going to be very difficult to debug when it goes wrong and it's going to. So why should you use See Go? Because you have to, basically. And it's really the only way to get what you want. I'm going to start off with calling See From Go, which is a little bit easier. Show of hands, how many people actually write See from time to time? All right. Well, not all of you, so the rest of you can tune out for the next couple of slides. This is a very basic See function. On the left is a header file. See separates the declaration of a function from the actual implementation. Think of it sort of like an interface, but a lot clunkier. So I define a function on the left and then I actually implement it on the right, making sure to include those headers. If you don't understand the types, unsigned integer, 32-bit, underscore type. In See, back then they had to use as few characters as possible, so everything is as confusing as you can imagine it being. This is what makes you know you're dealing with See Go. If you see import See somewhere in your stack, you're dealing with See Go. And it uses comments. So that's not ideal, perhaps. You're just like, oh, it's a comment. I could ignore it. You have to pay very close attention to it, and if you get the spaces wrong, it's probably not going to work, and it has to be exactly before import See, otherwise it won't work. But this is basically how it works. You'll notice that add one does not start with a capital letter. See is not a real module, so it can break rules like that. See is just a sort of pseudo module that says, hey, I want to call out from here. This is what the code itself looks like. I'm sorry it's very small, but the actual code is on the bottom of the slides. Important thing to note here, the import See always gets its own line, no matter how many other imports you have, because it's not actually an import. It's being used by the parser. So basically it's just a huge mess. Now if I run this, I'm going to get an error message. Everybody is always really disappointed when I don't do live demos because they don't get to see me screw up, so I've simulated demo syndrome for you. So you're going to need to do a cast here. That's that type I mentioned before. Even though they're both integer types, there is no coercion. You're going to have to coerce everything yourself. So here we go. 10 plus 1 is 11. It's a very, very difficult function. There's no way we could have implemented this in native Go. We had to use SeeGo. It was the only way. And now we're going to go back into calling Go from See. This is probably less likely to come up for Go programmers unless you're doing something kind of weird. I do some things that are kind of weird, so I have to deal with this. And I'm just going to start out with the export function at the top. Export means I want this to be callable externally. By default, nothing from Go code will be callable by an externality library. It is not a well-behaved citizen in that sense. So I'm going to export this. Side note, if you have an export statement, you can't have certain kinds of comments elsewhere in your file because it's compiled into a header and there will be a bunch of weird error messages and you won't understand. So keep your files separate. Here this is not the standard string that you might expect. This is a C string. And yes, there is a difference. So this is a string in C. It's a series of bytes terminated by a null byte, which is not the character zero. It is actually just zero, zero as bytes. And this is similar to how it works in Go, but not quite. Go stores a length and C just says, go until you find the end of it. If you've ever had a weird C program that just went off the rails, it's because you forgot to include the null byte and it just kept going forever. There are advantages to both. The main advantage to the C approach is that it is smaller and that matters sometimes. The main advantage to the Go approach is that it doesn't break all of the time. So this function takes that character and it turns it into a string that Go knows how to manipulate. And then we can put all of this back together again. If you want to export functions, you have to call the package main, even if it's not actually main. You have to have that little main stanza at the bottom, even though it doesn't actually do something. I'm sorry. It wasn't my fault. Now, here's what it's going to look like when we actually use it. You remember those header files I told you about with C? Well, we're going to define our own header files. Now let's take a look at it. Oh, there's some... Yeah, let's... Don't worry about it. It's technically text, but treated as a binary file. Don't look at it. I'm going to give you a brief introduction to make files because you're not going to be able to avoid it. I'm sorry. So this is sort of a typical make file. We've got outputs on the left, inputs on the right. That has to be a tab character, which is harder to explain to Python people. I think you go people already know that you should be using tabs. And there's a bunch of weird symbols there which are entirely un-Googleable. You're just going to have to trust me that dollars at means the at sign, which is the target that I'm pointing at, which is output in this case. And the other one means all of the input files. I just remember those two and I swapped them around until my program compiles. So this is what make is going to turn it into. Now, we've got a couple of different steps that we need. We're going to first create those header files because we need those header files so that we can actually call this function from C. We use the go tool to export header. There's a bunch of really good documentation on this, but this is sort of what to vaguely Google when you're trying to remember how you do this. We build a C archive, which is basically goes way of saying, this is code that looks kind of like C, so C files can use it. When you compile everything together, you just sort of like throw all of the things on the command line until it compiles all together. And here is how it actually works. So we have a binary called go from C. We take in main.C, which I showed you earlier, the complicated header file, and that archive of C code, of go code that we created. We throw it all together, and we add p thread on the end because you need the p thread library, and it's not included in cgo expert for some reason. Just it's there. You need to do it. Otherwise your code won't compile. It's how programming is sometimes. If I do make, it's going to compile all of these things together. You can see it's expanded all of those weird symbols that I used. Everything looks nice, perfect. Hey, that's you. Say hello to my fake code. All right, now we get into something very exciting, which is pointers and memory. Now, go looks a lot like C, but it has a lot of very big differences, but the biggest difference is that it's garbage collected. So when you create a new value in go, you can assume that it will be available until you don't need it anymore. With C, you basically have to assume the opposite. You have to assume it's already not available unless you want it to not be available, in which case you have to assume it's already available, and keep very careful track of this all in your head, and there's a reason we don't write C anymore. Well, some of us do, but there's a reason we prefer not to. So this is what allocating something in go looks like. You all know how to do this. This is what allocating it in C looks like. Somebody once told me that malloc was the Swiss army knife of C commands, and that's always how I remember that function. I don't think that's true. And here's how you free it. At the end, we use point, and it goes out of scope, and go is just like, ah, we don't need that anymore. We will get rid of it. Here, we use the point, and then we call free with it. If that free points to something that isn't a valid pointer, who knows what will happen. It's undefined. If you have already called free on it, maybe nothing will happen. Maybe it'll segfault. If somebody called free before they passed it to you, the code up there might actually segfault. When you actually, when you try to get the things. Sorry. Sorry. Bad cameras. What happens if you forget to call free? Nothing. It looks fine. But then you have to run this horrible program called Valgrind, which is going to tell you whether or not you've accidentally made a mistake somewhere, and it's going to have all this output, and it's going to say, oh, you failed to free something up, and it's pretty easy to track down in your tiny little program, but once things get a little bit bigger, it's going to get more complicated. That's why you used to have to restart your computer every couple of days, because it, like, leaked memory so badly. Right. Very different approaches to pointers. Very different approaches to memory management. What happens when you need to pass things between those two? Some things are okay. If you pass a go pointer to C and only use it while you are in the C code, that is fine. Same if you pass a C pointer to go. If you only use it briefly, that's fine. If you store a go pointer in C memory, you can do that, but only until you return, and there's a bunch of weird special cases in the documents for Java and Darwin, and invalid is basically everything else. Just be careful and read the C go documentation very carefully. I read it 10 or 20 times before I gave up, summarized it as best I can, and issued this stern warning to all of you. Now, this has all been counter-trivial. How about something a little bit more interesting? How about a little magic? Who likes using image magic? It's everybody's favorite library to use, right? This is an example very simple C image magic program. It takes in that wizard friend, which is defined as logo colon, and it outputs it to logo.jpeg, and that's all it does, and just doing that takes, like, 15 lines of C. So, how do we do this here? Well, it's actually not too bad to translate this. You put C dot in front of all of the functions before. You can actually use, oh, what's this? Package config. Another wonderful thing that people very much miss about having to deal with C. What is package config? Well, you need a lot of arguments to the C compiler to get image magic to work. You need all of these, otherwise it's going to yell at you, and rather than having to copy them in specifically, you can if you want to. This isn't all of them. I left out some of the defines, and the compiler will work, but it will yell at you, or you could just use the package config. It's much easier assuming it works. If it doesn't, I don't know what to do. Probably somebody in here does. And here's this little handy defer function. You can use some of these nifty year ago features when you're doing C. So rather than having to use go-to's or jumps or something to make sure your code gets cleaned up, just throw it in a defer. We're making things better already. And the full demo is on the GitHub page, which I keep throwing to. Now we're going to talk about something even more fun, dynamic libraries. Who has heard somebody say, go binaries are static? Who knows what that means? This is a specific crowd, I think. So dynamic libraries are, they have different extensions on different operating systems, but basically they're a bunch of code that is available for other programs to use, and there are specific ways that you can use them, and you have to assume that they'll be available at runtime. Some people think they are a mistake. So here is a pretty simple C program. I'm not doing anything dynamic yet. When I compile this, it's going to turn into a bunch of objects in memory that look something like this. So main gets stored at a specific address, local function gets stored at a specific address, and then they can call each other just fine. Now, what if I'm using some other library? I've included lib1 and lib2.h, and now I'm calling them both from local function. What is this going to look like? Well, the two functions that I know about are going to be defined properly, but the two shared functions are going to be undefined until I go to run the binary when the linker, the dynamic linker, is going to find them, search them out, and link them into the program properly so it can run. We're going to use a cool program called LDD. When a program has that few letters in it, we know it's going to be doing something obscure and frustrating. So this is the LDD output for Ginko, which is a common go testing utility. There's a lot of stuff we don't care about most of it. Get rid of all of those numbers. They don't mean anything to me. These are the common dependencies you will see in most go binaries. You need C, you need LD, which is the library that allows you to use dynamic link libraries, which is fun. You need threads. Remember how I added threads in up there? This is why you need it, probably. And whatever VDSO is, I don't know. So we're going to add this library in that we defined using dash L ad lib. It's not lad lib, even though that might be fun. It's dash L ad lib, and there's no space because, remember, this is C. Characters are very precious. So we're going to install this. This is not actually the command you use. The command you use is something like this, because you have to get the files right, and the libraries you're looking for probably aren't on the right path. This is the important part. Dash I adds a path to look for header files. Dash L adds a path to look for actual compiled library files. And then, only then can you install this library. You can see here one of these isn't found. This is how you can tell without running a program whether or not it's going to actually run properly. That not found means this program is not going to run properly. But I can pass in a specific path so that it will run properly. Just pass in this long variable on the command line. Side note, you can do some really fun stuff with this because you can insert your own libraries for things like libc and pthread and do all sorts of nasty things to executables that aren't suspecting it. That is another talk for another time. I have about five minutes left and now I am happy to take any questions that anybody has. Questions, comments, thinly veiled insults, anyone? Sorry, what was that? Can you make a static binary using a dynamic library? Yes. Can you make a static binary using a dynamically linked library? Yes, you can. In fact, that is what Go is going to try to do by default. But sometimes you will need to use these things in different places depending on your specific needs. This is something that I ran into and I found very frustrating, so this was how I figured it out. Anyone else? Oh, over there. The motivating example for... Oh, yes. What are some examples of situations where you basically have to use C and there aren't any other options available? The motivating example for me to learn Cgo was writing Postgres extensions. Postgres is written in C and C++. You can write extensions in C or C++ and if you do that, then you can mostly write the extension in Go and have a little bit of wrapper code and for what I was doing, which was Kubernetes-related, that was much easier for me. Some other examples are anything to do with GUIs. It might require a binary thing. Some really nasty library like ImageMagic or something like that. I think the TensorFlow library that was mentioned last time probably had to do some Cgo stuff behind the scenes. A good library will wrap all of this so you don't have to worry about it, but behind the scenes something like this is probably happening. Anyone else? Oh, over there. Yes, you can't... Yes. Can you use all of the Go features when you are writing in Go and Cgo? Kind of. It's difficult to use things like using channels inside of C code is sometimes possible, sometimes not, but if you're calling C code from Go, that Go code can use any language features you want. It's just when you pass some of the more complicated Go constructs across the C barrier, that's when things get a little bit complicated. And more information in that is the Cgo docs have a really great list of what you can and can't pass across that barrier. Yes, in the back. Yes, have I noticed any differences where the C is better than the Go? Mostly the difference is that it's better at being able to do the thing that I need it to do because it can and Go can't. I don't really like writing C on purpose because it seg faults a whole bunch and it's really frustrating. So I can't think of a reason why you would use Cgo unless you absolutely had to. I don't think I noticed it being particularly faster or anything like that, but again, you saw my examples, they were all very trivial. So there are probably some Go veterans out there who can answer your question better than I can. I think I had one over here. Any advice on testing a Cgo project? Do it. You can test C, you can test Go. No specific advice. All of the projects that I tested, I tested by running the code a whole bunch and hoping it didn't fail when I was on stage. Any performance issues? Any performance issues in terms of FFI overhead? Probably. Sorry, I know enough about this to give this talk, but I am not an expert in profiling Go or any of that sort of stuff. So there's probably somebody in here who is. That's the Go dev room. Just go up to random people in this room and bother them until you find somebody who knows the right questions. Make some new friends. I've used Rust interacting with C. It's a little bit easier because Rust, just like it's much more similar to C. There aren't problems quite as much with the pointers and that sort of thing. I've also used the Python FFI, and that is worse because you have to manage all of the explicit reference counting yourself. Basically like manually incrementing and decrementing reference counts as you do that. There is Scython, which is different from what I used. I've heard that's a lot nicer. So I would put Go somewhere in the middle of the FFIs that I've used. And I am out of time, so if anybody wants to ask me more questions, there's not a lot of people here with pink hair I should be easy to find. And enjoy the rest of your conference, everyone.