Why Use Underscore Fields in Go Structs
Daniel Hayes
Full-Stack Engineer · Leapcell

Preface
In the Go programming language, we often see the use of the underscore (_
), such as using it as a placeholder to ignore unwanted variables, importing packages solely for their side effects, or ignoring variables in type conversions. However, most people may not have encountered the use of an underscore within a struct—specifically, defining a struct field named _
.
So, what is the purpose of defining such a field?
Code Comparison: Structs With and Without Underscore (_
) Fields
First, let's look at an example of a struct without an underscore (_
) field.
In the model
package, we define a User
struct with two fields: Name
and Age
.
type User struct { Name string Age int }
We declare struct variables using both positional and named field initialization.
user := model.User{"Alice", 18} user = model.User{Name: "Alice", Age: 18}
In the above code, there are no issues with either defining or declaring the struct.
Now, let's look at an example where the struct includes an underscore (_
) field:
In the model
package, we define a User
struct with three fields: Name
, Age
, and _
.
type User struct { Name string Age int _ struct{} }
We declare struct variables using both positional and named field initialization.
// Compilation error: too few values in struct literal of type model.User user := model.User{"Alice", 18} // Compilation error: implicit assignment to unexported field _ in struct literal of type model.User user = model.User{"Alice", 18, struct{}{}} // Valid user = model.User{} user = model.User{Name: "Alice", Age: 18}
In the example above, if you declare a struct variable using user := model.User{"Alice", 18}
or model.User{"Alice", 18, struct{}{}}
, which are both positional initialization methods, the program will produce compilation errors. However, using zero-value initialization or named field initialization will work without issue.
By comparing the struct examples with and without the underscore (_
) field, we can conclude the purpose of defining such a field: Adding a field named _
in a struct effectively forces the struct to be initialized using named field initialization (except in cases of zero-value struct variable declaration).
Brief Analysis of the Principle
When we declare a struct using positional initialization, we need to provide values for all fields in the exact order they are defined.
If a struct includes a field named _
, and we use positional initialization without supplying a value for the _
field, the compiler will produce an error like:
too few values in struct literal of type XXX
This happens because not all field values were provided.
Even if we provide values for all fields in the correct order, the compiler will still throw an error:
implicit assignment to unexported field _ in struct literal of type XXX
This is because the _
field starts with a lowercase letter and is thus considered unexported. We cannot assign a value to an unexported field using positional initialization. As a result, it's not possible to initialize this struct in this way.
In summary, because struct variables with an underscore (_
) field cannot be declared using positional initialization, we are left with only two valid approaches: zero-value initialization or named field initialization.
Conclusion
Through this discussion, we have learned about the special use of an underscore (_
) as a field name in Go struct definitions.
Specifically, defining a field named _
can effectively enforce named field initialization when creating instances of the struct, preventing the use of positional initialization. The advantages of this approach include:
- Code readability: Named field initialization enhances readability and maintainability by explicitly associating each value with a field name.
- Error prevention: Positional initialization relies on strict adherence to field order, making it error-prone. Named field initialization eliminates this risk.
We are Leapcell, your top choice for hosting Go projects.
Leapcell is the Next-Gen Serverless Platform for Web Hosting, Async Tasks, and Redis:
Multi-Language Support
- Develop with Node.js, Python, Go, or Rust.
Deploy unlimited projects for free
- pay only for usage — no requests, no charges.
Unbeatable Cost Efficiency
- Pay-as-you-go with no idle charges.
- Example: $25 supports 6.94M requests at a 60ms average response time.
Streamlined Developer Experience
- Intuitive UI for effortless setup.
- Fully automated CI/CD pipelines and GitOps integration.
- Real-time metrics and logging for actionable insights.
Effortless Scalability and High Performance
- Auto-scaling to handle high concurrency with ease.
- Zero operational overhead — just focus on building.
Explore more in the Documentation!
Follow us on X: @LeapcellHQ