//go:embed works by having the Go compiler read specified files at compile time and embed their contents into the binary’s .rodata section. 1) The compiler processes //go:embed directives and reads the listed files relative to the source file. 2) It assigns the content to a variable of type string, []byte, or embed.FS, converting byte slices to strings if needed. 3) For embed.FS, the compiler builds an in-memory file tree storing paths, contents, modes, and modification times. 4) This data is stored in the binary’s read-only section, enabling use via fs.FS methods without runtime dependencies. 5) The directive respects build tags and only includes local files within the module, ensuring self-contained, portable binaries. The embed package provides only the FS type, while the actual embedding is handled by the compiler and linker, making it compile-time asset inclusion akin to constants for files.
The //go:embed
directive in Go works by allowing the Go compiler to include static files—like text, JSON, HTML, or binary assets—directly into the compiled binary at build time. But how does it actually work under the hood? Let’s break it down.

It’s a Compiler Feature, Not Runtime Magic
First and most importantly: embed
is implemented primarily in the Go compiler and linker, not in the embed
package itself. The embed
package ("embed"
) only provides one type: fs.FS
, which is an interface for file systems. The real magic happens via the special comment //go:embed
.
When the Go compiler sees a //go:embed
directive followed by a variable declaration, it:

- Parses the directive during compilation.
- Reads the specified files from the filesystem (relative to the Go source file).
- Stuffs the file contents into a special readonly data section of the binary.
-
Assigns that data to the associated variable (usually of type
string
,[]byte
, orembed.FS
).
This all happens at compile time, so no external files are needed at runtime.
How the Data Gets Embedded
Here’s a typical example:

package main import "embed" //go:embed hello.txt var content string func main() { println(content) }
Behind the scenes:
- The compiler sees
//go:embed hello.txt
and knows it needs to includehello.txt
. - It reads the file and stores its contents as a readonly byte slice in the binary.
- If the target variable is
string
, the compiler generates code to convert the byte slice to a string at program startup. - For
embed.FS
, the compiler builds an in-memory representation of the directory structure, mapping file paths to their embedded content.
This embedded data lives in the .rodata
(read-only data) section of the binary, just like constant strings and other literals.
What Happens with embed.FS
?
When you embed multiple files into an embed.FS
:
//go:embed *.txt var files embed.FS
The compiler constructs a compile-time file tree. It records:
- File paths
- File contents
- File modes (based on source file permissions)
- Modification times (based on source file, if available)
This tree becomes an immutable embed.FS
value that implements fs.FS
, fs.ReadDirFS
, and fs.ReadFileFS
. You can use files.Open()
, fs.ReadFile()
, etc., just like with a real filesystem.
Internally, Go uses a compact structure to represent this tree—essentially a map of paths to file data, baked into the binary.
Build Constraints and Embedding
The //go:embed
directive respects build tags and file system layout. Only files that would be included in the build (based on GOOS, GOARCH, etc.) are considered. Also, you can't embed .go
files from the same package unless they’re excluded via build tags.
And importantly: the files must be local—you can’t embed files outside the module or from arbitrary paths.
Summary of Key Points
-
//go:embed
is processed by the compiler, not the runtime. - Files are read at compile time and stored in the binary’s
.rodata
. - Supported types:
string
,[]byte
,embed.FS
. - For
embed.FS
, Go builds an in-memory file tree with path-to-content mapping. - No external dependencies at runtime—the binary is self-contained.
So while it feels like magic, it’s really just smart compile-time asset baking. The result? Fast, portable binaries with zero reliance on external files.
Basically, it's like const
for whole files.
The above is the detailed content of How does go embed work under the hood?. For more information, please follow other related articles on the PHP Chinese website!

Hot AI Tools

Undress AI Tool
Undress images for free

Undresser.AI Undress
AI-powered app for creating realistic nude photos

AI Clothes Remover
Online AI tool for removing clothes from photos.

Clothoff.io
AI clothes remover

Video Face Swap
Swap faces in any video effortlessly with our completely free AI face swap tool!

Hot Article

Hot Tools

Notepad++7.3.1
Easy-to-use and free code editor

SublimeText3 Chinese version
Chinese version, very easy to use

Zend Studio 13.0.1
Powerful PHP integrated development environment

Dreamweaver CS6
Visual web development tools

SublimeText3 Mac version
God-level code editing software (SublimeText3)

Hot Topics

The most efficient way to write a KubernetesOperator is to use Go to combine Kubebuilder and controller-runtime. 1. Understand the Operator pattern: define custom resources through CRD, write a controller to listen for resource changes and perform reconciliation loops to maintain the expected state. 2. Use Kubebuilder to initialize the project and create APIs to automatically generate CRDs, controllers and configuration files. 3. Define the Spec and Status structure of CRD in api/v1/myapp_types.go, and run makemanifests to generate CRDYAML. 4. Reconcil in the controller

Go does not have a built-in collection type, but it can be implemented efficiently through maps. Use map[T]struct{} to store element keys, empty structures have zero memory overhead, and the implementation of addition, inspection, deletion and other operations are O(1) time complexity; in a concurrent environment, sync.RWMutex or sync.Map can be combined to ensure thread safety; in terms of performance, memory usage, hashing cost and disorder; it is recommended to encapsulate Add, Remove, Contains, Size and other methods to simulate standard collection behavior.

UselightweightrouterslikeChiforefficientHTTPhandlingwithbuilt-inmiddlewareandcontextsupport.2.Leveragegoroutinesandchannelsforconcurrency,alwaysmanagingthemwithcontext.Contexttopreventleaks.3.OptimizeservicecommunicationbyusinggRPCwithProtocolBuffers

Usemulti-stageDockerbuildstocreatesmall,secureimagesbycompilingtheGobinaryinabuilderstageandcopyingittoaminimalruntimeimagelikeAlpineLinux,reducingsizeandattacksurface.2.Optimizebuildperformancebycopyinggo.modandgo.sumfirsttoleverageDockerlayercachin

Go and Kafka integration is an effective solution to build high-performance real-time data systems. The appropriate client library should be selected according to needs: 1. Priority is given to kafka-go to obtain simple Go-style APIs and good context support, suitable for rapid development; 2. Select Sarama when fine control or advanced functions are required; 3. When implementing producers, you need to configure the correct Broker address, theme and load balancing strategy, and manage timeouts and closings through context; 4. Consumers should use consumer groups to achieve scalability and fault tolerance, automatically submit offsets and use concurrent processing reasonably; 5. Use JSON, Avro or Protobuf for serialization, and it is recommended to combine SchemaRegistr

Go's template engine provides powerful dynamic content generation capabilities through text/template and html/template packages, where html/template has automatic escape function to prevent XSS attacks, so it should be used first when generating HTML. 1. Use {{}} syntax to insert variables, conditional judgments and loops, such as {{.FieldName}} to access structure fields, {{if}} and {{range}} to implement logical control. 2. The template supports Go data structures such as struct, slice and map, and the dot in the range represents the current iterative element. 3. The named template can be defined through define and reused with the template directive. 4.ht

When passing slices in Go, it is usually passed directly by value, because the slice header contains a pointer to the underlying array, and copying the slice header will not copy the underlying data, so the modification of elements in the function will affect the original slice; 1. If you need to reassign or adjust the slice length within the function and make the change take effect, you should pass the slice pointer; 2. Otherwise, you can pass the slice directly without using a pointer; 3. If reallocation may be triggered when using append, you must pass through the pointer to make the updated slice visible to the outside. Therefore, unless the entire slice is to be replaced, the slice should be passed in the form of a value.

govetcatchescommonlogicalerrorsandsuspiciousconstructsinGocodesuchas1)misuseofprintf-stylefunctionswithincorrectarguments,2)unkeyedstructliteralsthatmayleadtoincorrectfieldassignments,3)sendingtoclosedchannelswhichcausespanics,4)ineffectiveassignment
