Go
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Functions and Methods
-
Section 4: Concurrency
-
Section 5: Packages and Tooling
-
Section 6: More Standard Library
-
Section 7: Building Services
-
Section 8: Advanced Go
-
Section 9: Go in the Cloud-Native Ecosystem
-
Section 10: Data Structures and Algorithms in Go
-
Section 11: Testing and Deployment
-
Section 12: Practical Projects
-
Section 13: More Standard Library Practice
-
Section 14: More Practice Projects
-
Section 15: Design Patterns in Go
-
Section 16: Interview Practice
-
Section 17: Package fmt In Depth
-
Section 18: Package strings and strconv
-
Section 19: Package os and io
-
Section 20: Package time
-
Section 21: Package sort and container
-
Section 22: Package encoding
-
Section 23: Package net/http In Depth
-
Section 24: Package context
-
Section 25: Package regexp and bytes
-
Section 26: Package errors In Depth
-
Section 27: Package crypto and hash
-
Section 28: Package flag and log
-
Section 29: Package sync In Depth
-
Section 30: More Practice Exercises
-
Section 31: Go Modules and Workspaces In Depth
-
Section 32: Generics Deep Dive (Go 1.18+)
-
Section 33: Testing Package In Depth
-
Section 34: More Interview and Whiteboard Practice
-
Section 35: Package math and unicode
-
Section 36: Package path and filepath
-
Section 37: Package database/sql
-
Section 38: Package text/template and html/template
-
Section 39: Package archive and compress
-
Section 40: Lower-Level net Package
-
Section 41: Package runtime and reflect
-
Section 42: Package embed and io/fs
-
Section 43: Go Assembly and CGO Basics
-
Section 44: Building CLIs and TUIs
-
Section 45: Go Performance Tuning
-
Section 46: More Real-World Projects
-
Section 47: Go in Production
-
Section 48: Go Design Patterns
-
Section 49: Go Interfaces Deep Dive
-
Section 50: Final Practice and Review
211: Calling C Code from Go with cgo
Look, cgo is one of those features you hope you don't need, but when you're interfacing with a legacy C library or a high-performance system API, it's an absolute lifesaver. It's not as seamless as calling a native Go function, though. You're essentially stepping out of Go's managed world and into the "wild west" of manual memory management. Let's get you comfortable with it.
How do I actually tell Go to use C code?
The magic happens with a "pseudo-package" called C. But here is the catch: you can't just import it. You have to provide a preamble—a block of C code written in a comment immediately preceding the import "C" line. If there's even a single empty line between your comment and the import, the compiler will ignore it and you'll get a bunch of undefined errors.
Suppose we have a legacy C function that calculates a simple checksum of a byte array. Here is how you'd set that up in a Go file:
package main
/*
#include <stdio.h>
int calculate_checksum(const char* data, int len) {
int sum = 0;
for (int i = 0; i < len; i++) {
sum += (unsigned char)data[i];
}
return sum;
}
*/
import "C"
import "fmt"
func main() {
// We'll see how to call this in the next section
}
How do I pass a Go string or slice into a C function?
This is where most people trip up. You can't just pass a Go string to a C function because Go strings are managed by the garbage collector (GC), while C expects a pointer to a null-terminated array of bytes. If the GC moves your string around while C is reading it, you've got a memory corruption nightmare on your hands.
To fix this, you use C.CString. This function allocates a new block of memory in the C heap and copies the Go string into it. Because this memory is outside the reach of the Go GC, you must free it manually using C.free. I can't stress this enough: every C.CString needs a corresponding C.free.
package main
/*
#include <stdlib.h>
int calculate_checksum(const char* data, int len) {
int sum = 0;
for (int i = 0; i < len; i++) {
sum += (unsigned char)data[i];
}
return sum;
}
*/
import "C"
import (
"fmt"
"unsafe"
)
func GetChecksum(input string) int {
// Allocate C memory for the string
cStr := C.CString(input)
// Ensure we free the memory when the function returns
defer C.free(unsafe.Pointer(cStr))
// Call the C function
// We cast the length of the Go string to a C.int
result := C.calculate_checksum(cStr, C.int(len(input)))
return int(result)
}
func main() {
fmt.Println("Checksum:", GetChecksum("Hello cgo!"))
}
Why can't I just use regular int or string types?
You'll notice I used C.int and C.CString. Go is very strict about types, and a C int isn't necessarily the same size as a Go int (which depends on whether you're on a 32-bit or 64-bit architecture). To prevent silent overflows or memory misalignment, cgo creates a separate set of types in the C namespace.
Whenever you're passing data back and forth, you'll be doing a lot of explicit casting. It feels verbose, but it's there to make sure you're consciously acknowledging the boundary between the two languages. If you're dealing with pointers, you'll also find yourself using the unsafe.Pointer type, which acts as a universal bridge that can be cast to any other pointer type.
Is calling C code always faster than writing it in Go?
Actually, no. There is a non-trivial overhead associated with every cgo call. When Go calls C, the runtime has to switch stacks, save registers, and disable certain scheduler optimizations. If you're calling a C function that does something tiny—like adding two numbers—you'll actually find that the Go version is significantly faster because it avoids that "border crossing" penalty.
I usually advise my team to only use cgo when they are calling a massive existing library (like OpenSSL or SQLite) or performing a heavy computational task where the time spent inside the C function vastly outweighs the cost of the call itself.
📋 Practical Task
Exercise: Building a C-Powered String Reverser
In this exercise, you will create a Go program that leverages a C function to reverse a string in-place. This will test your ability to handle C memory allocation and type conversion.
- Create a Go file that includes a C function
void reverse_string(char* str)in the preamble. The C function should take a pointer to a string and reverse its characters. - Write a Go wrapper function called
Reverse(input string) string. - Inside the wrapper, convert the Go string to a
C.CString. - Call the
reverse_stringfunction. - Convert the resulting C string back into a Go string using
C.GoString. - Crucial: Ensure you use
C.freeto prevent memory leaks. - Test your program with the input
"Gophers are awesome!"and verify the output is"!emosewa era srehpoG".
There are no comments for now.