What does the special import "C" line do in a Go file, and what is the comment above it?
answer
- not a package on disk
- the comment right above it matters
- no blank line is allowed there
- that comment is compiled as C
- C names appear under a C. prefix
basics
~20 simport "C" is not a real package; it turns on cgo for that file. The comment directly above it, with no blank line between, is the preamble: C code that is compiled and exposed to Go as C.name.
solid answer
~50 s`import "C"` is a pseudo-import, not a package on disk and nothing `go get` fetches. It tells the go command to run cgo on that file, and it makes a synthetic package `C` available whose members are the C declarations visible in the **preamble** — the comment block immediately above the import, with no blank line between them. Anything you put there (`#include <stdlib.h>`, a `static` helper function, a `#define`) is compiled by the C compiler, and each C type, function, variable and macro becomes `C.int`, `C.free`, `C.myHelper` and so on. Because it is not a real package, `C` cannot be aliased, cannot be imported by a different package, and its types do not cross package boundaries — you wrap them behind ordinary Go types. Building such a file needs a working C toolchain on the build machine.
code
go · 19 linespackage main
/*
#include <stdio.h>
#include <stdlib.h>
static void greet(const char *name) {
printf("hello, %s\n", name);
}
*/
import "C"
import "unsafe"
func main() {
cs := C.CString("world")
defer C.free(unsafe.Pointer(cs))
C.greet(cs)
}go deeper
Be ready to say that import "C" is a marker rather than a package, and to point at the comment directly above it as the C code being compiled. Knowing that no blank line may sit between them is the detail that gets checked.
Explain the mechanics: the go command runs cgo, the preamble is compiled by a C compiler, and each C declaration surfaces as C.name. Be able to show why C.int(n) is required rather than an implicit conversion from Go's int.
Show that you keep C. types inside one thin wrapper package and expose only plain Go types, because each package importing "C" gets its own incompatible C types. Mention that adding cgo changes what the build machine must provide.
Frame it as a build-and-dependency decision, not a coding one: switching a package to cgo commits every consumer's build to a C toolchain and the headers it needs. Be prepared to argue when a pure-Go alternative is worth the porting cost instead.
## What `import "C"` actually is Go has no foreign-function-interface syntax. Instead, the toolchain ships a program called **cgo**, and the trigger that runs it is a single line in a source file: ```go import "C" ``` There is no package named `C` anywhere on disk, in the module cache, or in the standard library. The line is a marker. When the go command sees it while building a package, it hands that file to `cgo` (you can invoke it yourself as `go tool cgo`), which reads the C declarations you supplied, generates Go stubs and C glue, compiles the C parts with a C compiler, and links everything into the final binary. ## The preamble The C declarations come from the **preamble**: the comment immediately preceding `import "C"`, with **no blank line** between the end of the comment and the import statement. That adjacency is the whole rule — a blank line turns the comment into an ordinary Go doc comment, cgo never compiles it, and every `C.something` in the file becomes an undefined reference. ```go package codec /* #include <stdlib.h> #include "codec.h" static int codec_version_major(void) { return CODEC_VERSION >> 16; } */ import "C" ``` The preamble is real C, compiled as C. It can contain `#include` directives, macros, typedefs, and — commonly — small `static` helper functions that paper over things cgo cannot express directly, such as calling a variadic C function or dereferencing a macro. ## What the pseudo-package exposes After the preamble is compiled, everything it declares is reachable through the `C.` prefix: - **Types**: `C.int`, `C.char`, `C.long`, `C.size_t`, `C.double`, plus any typedef or struct you included. A C struct `struct codec_ctx` is `C.struct_codec_ctx`; a union becomes an opaque byte array; a field whose name collides with a Go keyword gains a leading underscore. - **Functions**: `C.free`, `C.codec_version_major`, and anything else declared in the headers you pulled in. - **Variables and macros**: an `enum` constant or a simple `#define` of a constant value is usable as `C.CODEC_OK`. Two conversion helpers are worth knowing immediately because you will reach for them in the first hour: `C.CString` copies a Go string into C-allocated memory and returns `*C.char`, and `C.GoString` copies a NUL-terminated `*C.char` back into a Go string. ## Numeric types do not convert implicitly Go's `int` is not C's `int`. On most platforms Go's is 64-bit while C's is 32-bit, and cgo will not silently bridge them — the conversion is explicit and it is a genuine conversion, not a cast: ```go n := 4096 C.codec_set_buffer(C.int(n)) goBack := int(C.codec_get_buffer()) ``` The same applies to `C.char` versus Go's `byte`, and to `C.size_t` versus `uint`. Forgetting this is the most common first compile error a newcomer hits. ## The errno form Any C call may be written in a two-value assignment. The second value is the C `errno` variable after the call, delivered as a Go `error`: ```go rc, err := C.codec_open(path) ``` This is only meaningful when the C function's own return value already told you it failed — `errno` is not cleared by a successful call, so reading it unconditionally produces stale, confusing errors. ## Boundaries of the pseudo-package Because `C` is synthetic, several ordinary Go moves do not work: - You cannot write `import c "C"` to alias it. - You cannot export a `C.int` or a `*C.char` from your package and expect another package to use it — each package that imports `"C"` gets its *own* `C` types, and they are not interchangeable. The idiomatic shape is a thin wrapper package whose public API is pure Go (`func Encode(s string) (string, error)`) with all `C.` traffic hidden inside. - Reflection over `C.` types is not something to rely on. ## What it costs to switch on Adding `import "C"` to a package changes the build, not just the code: the package now requires a C compiler at build time, and the resulting binary is no longer produced by the Go toolchain alone. That is a real decision, not a detail — which is why teams that want a C library often first ask whether a pure-Go equivalent exists. ## What an interviewer is listening for They want to hear that `C` is not a package, that the preamble is adjacency-sensitive, that the C names appear under a `C.` prefix, and that numeric conversions are explicit. A candidate who says "you `go get` the C package" or who cannot say where the C code physically lives has not used cgo.
- Why must there be no blank line between the preamble comment and import "C"?Adjacency is how cgo identifies the preamble. With a blank line the comment is just a Go doc comment, its `#include` lines and helper functions are never compiled, and every `C.` reference in the file fails to resolve. There is no warning telling you the comment was demoted — you get undefined-name errors instead.
- How do you pass a Go int variable to a C function that takes an int?Convert explicitly: `C.int(n)`. Go's `int` is typically 64-bit while C's `int` is usually 32-bit, and cgo performs no implicit numeric conversion. The same discipline applies coming back — wrap the result in `int(...)`, and use `C.long`, `C.size_t` or `C.char` when the C signature calls for them.
- What does the two-result form rc, err := C.some_call() give you?Any C function call may be used in a multiple-assignment context; the second value is the C `errno` after the call, converted to a Go `error`. Only consult it when the C function's own return value already indicates failure, because `errno` is not reset on success and you would otherwise report a stale error from an earlier call.
The preamble is less like an import list and more like a header file glued to the top of your Go file: whatever it declares is what the C. prefix can reach.
saying these in an interview costs you the question
- Says C is a real package fetched by go get
- Puts a blank line between the preamble and import "C"
- Assumes a Go int converts to C.int automatically
- Expects C. types to be usable from another package
- Thinks cgo needs no C compiler at build time
- Calls the preamble a normal doc comment