Boolean — Middle Level¶
Table of Contents¶
- Introduction
- Prerequisites
- Glossary
- Core Concepts
- Real-World Analogies
- Mental Models
- Pros & Cons
- Use Cases
- Code Examples
- Coding Patterns
- Clean Code
- Product Use / Feature
- Error Handling
- Security Considerations
- Performance Tips
- Metrics & Analytics
- Best Practices
- Edge Cases & Pitfalls
- Common Mistakes
- Common Misconceptions
- Tricky Points
- Test
- Tricky Questions
- Cheat Sheet
- Self-Assessment Checklist
- Summary
- What You Can Build
- Further Reading
- Related Topics
- Diagrams & Visual Aids
- Evolution & Historical Context
- Alternative Approaches
- Anti-Patterns
- Debugging Guide
- Comparison with Other Languages
Introduction¶
Focus: "Why?" and "When to use?"
At the middle level, understanding bool goes beyond "it's true or false." You need to understand when booleans are the right tool, when they become a liability, and how Go's design philosophy around booleans affects system design.
Go's boolean type enforces explicit intent. Unlike C, Python, or JavaScript, Go won't coerce other types into booleans. This forces developers to be precise about what "truthy" means in their domain.
package main
import "fmt"
// Bool used correctly: clear semantic meaning
type Order struct {
ID int
IsPaid bool
IsShipped bool
IsCancelled bool
}
func (o Order) CanShip() bool {
return o.IsPaid && !o.IsCancelled && !o.IsShipped
}
func main() {
order := Order{ID: 42, IsPaid: true}
fmt.Println("Can ship:", order.CanShip()) // true
}
Understanding when to use bool vs other constructs (enums, iota, state machines) is key at this level.
Prerequisites¶
- Solid understanding of boolean basics (Junior level)
- Go structs and methods
- Interfaces in Go
- Error handling patterns
- Basic testing with
testingpackage
Glossary¶
| Term | Definition |
|---|---|
| Boolean blindness | When function parameters are bool values and callers can't tell what they mean |
| Predicate | A function that returns a bool; tests a condition |
| Short-circuit | Evaluation that stops as soon as the result is certain |
| Flag parameter | A boolean argument to a function — generally an anti-pattern |
| State machine | A system where an entity transitions between discrete states (better than multiple booleans) |
| Bit packing | Storing multiple booleans in a single integer using bitwise ops |
| Boolean accumulator | Collecting boolean results over time (e.g., allValid := allValid && check()) |
Core Concepts¶
1. Why Go Doesn't Allow Implicit Bool Conversion¶
In C: if (x) { } works even when x is an int. In Python: if []: evaluates to False.
Go chose strict typing:
var x int = 1
if x { } // COMPILE ERROR: non-bool x used as bool
if x != 0 { } // CORRECT: explicit comparison
Why? To eliminate a class of bugs. In C, if (a = b) (assignment, not comparison) compiled silently and was a common bug. Go's explicit requirement forces clarity.
2. Bool in Concurrent Code¶
Booleans accessed from multiple goroutines need synchronization:
import "sync/atomic"
// WRONG: data race
var isRunning bool
// CORRECT: atomic operation
var isRunning int32 // use 0 and 1
func setRunning(b bool) {
val := int32(0)
if b { val = 1 }
atomic.StoreInt32(&isRunning, val)
}
func getRunning() bool {
return atomic.LoadInt32(&isRunning) != 0
}
Or use sync.Mutex:
var (
mu sync.Mutex
isRunning bool
)
func setRunning(b bool) {
mu.Lock()
defer mu.Unlock()
isRunning = b
}
3. The ok Idiom¶
Go's multi-return pattern with bool is idiomatic:
// Map lookup
value, ok := myMap[key]
if !ok {
// key not found
}
// Type assertion
s, ok := i.(string)
if !ok {
// not a string
}
// Channel receive
v, ok := <-ch
if !ok {
// channel closed
}
4. Boolean Accumulation Pattern¶
func validateAll(user User) bool {
valid := true
valid = valid && len(user.Name) > 0
valid = valid && len(user.Email) > 0
valid = valid && user.Age >= 18
return valid
}
// Or using short-circuit:
func validateAll(user User) bool {
return len(user.Name) > 0 &&
len(user.Email) > 0 &&
user.Age >= 18
}
5. Returning Multiple Booleans vs. State¶
When you have too many booleans, consider a state type:
// Problematic: order state scattered across multiple booleans
type Order struct {
IsPending bool
IsPaid bool
IsShipped bool
IsDelivered bool
IsCancelled bool
IsRefunded bool
}
// Better: use a state type
type OrderStatus int
const (
StatusPending OrderStatus = iota
StatusPaid
StatusShipped
StatusDelivered
StatusCancelled
StatusRefunded
)
type Order struct {
Status OrderStatus
}
Real-World Analogies¶
Boolean as a light switch vs. a dimmer: For two-state situations, a bool is perfect. But if you need "dimming levels," you need an integer or enum. Choosing bool is choosing a light switch, not a dimmer.
Short-circuit as a loan application: The bank first checks if you have income (cheap check). If you don't, they don't bother checking your credit score (expensive check). hasIncome && goodCreditScore — put the cheap check first.
The ok idiom as a receipt: When you buy something, you get a receipt. value, ok := store.Get(key) — the ok is your receipt confirming the transaction succeeded.
Mental Models¶
Model 1: Booleans as Flags in a State Machine
Each bool in a struct is a "flag" in a state machine. Too many flags and the state machine becomes unmanageable. Rule of thumb: more than 2-3 boolean flags often means you need an explicit state type.
Model 2: Short-Circuit as Pipeline
A && B && C
↓
[A] → false? → stop, return false
↓ true
[B] → false? → stop, return false
↓ true
[C] → return C's value
Model 3: The "Semantic Gap"
If you find yourself writing if !isNotInactive, there's a semantic gap. Rename to eliminate double negatives.
Pros & Cons¶
Pros¶
- Explicit, type-safe boolean conditions
- Short-circuit prevents nil dereferences and division by zero
- Zero value (
false) is the safe default - The
okidiom enables clean error-free lookups - Atomic boolean operations are straightforward
Cons¶
- Boolean blindness:
func create(validate, notify, cache bool)— confusing call sites - State explosion: 5 booleans = 32 possible states (2^5), hard to reason about
- No bitwise bool operations: Go booleans don't support
&,|(only&&,||) - Concurrency: non-atomic reads/writes cause data races
Use Cases¶
- Feature toggles — runtime feature flags with zero value safety
- Validation pipelines — chaining validators with
&& - Map/channel existence checks — the
okidiom - Goroutine lifecycle —
done chan boolor atomic flags - Configuration structs —
type Config struct { Debug bool; Verbose bool } - ACL / permission system —
map[string]boolfor permissions - Circuit breaker —
isOpen boolto reject requests when a service is down
Code Examples¶
Example 1: The ok Idiom in Practice¶
package main
import "fmt"
type Cache struct {
data map[string]string
}
func NewCache() *Cache {
return &Cache{data: make(map[string]string)}
}
func (c *Cache) Get(key string) (string, bool) {
val, ok := c.data[key]
return val, ok
}
func (c *Cache) Set(key, value string) {
c.data[key] = value
}
func main() {
cache := NewCache()
cache.Set("user:1", "Alice")
if name, ok := cache.Get("user:1"); ok {
fmt.Println("Found:", name)
}
if _, ok := cache.Get("user:2"); !ok {
fmt.Println("Not found: user:2")
}
}
Example 2: Boolean Accumulation with Early Exit¶
package main
import (
"fmt"
"strings"
)
type User struct {
Name string
Email string
Age int
}
type ValidationError struct {
Field string
Message string
}
func validateUser(u User) []ValidationError {
var errs []ValidationError
if len(u.Name) == 0 {
errs = append(errs, ValidationError{"Name", "cannot be empty"})
}
if !strings.Contains(u.Email, "@") {
errs = append(errs, ValidationError{"Email", "invalid format"})
}
if u.Age < 18 {
errs = append(errs, ValidationError{"Age", "must be 18 or older"})
}
return errs
}
func isValid(u User) bool {
return len(validateUser(u)) == 0
}
func main() {
u := User{Name: "Alice", Email: "alice@example.com", Age: 25}
fmt.Println("Valid:", isValid(u)) // true
u2 := User{Name: "", Email: "not-an-email", Age: 15}
errs := validateUser(u2)
for _, e := range errs {
fmt.Printf(" %s: %s\n", e.Field, e.Message)
}
}
Example 3: Goroutine Done Flag¶
package main
import (
"fmt"
"sync/atomic"
"time"
)
type Worker struct {
running int32 // 0 = stopped, 1 = running
}
func (w *Worker) Start() {
if !atomic.CompareAndSwapInt32(&w.running, 0, 1) {
fmt.Println("Already running")
return
}
go func() {
defer atomic.StoreInt32(&w.running, 0)
fmt.Println("Worker started")
time.Sleep(100 * time.Millisecond)
fmt.Println("Worker done")
}()
}
func (w *Worker) IsRunning() bool {
return atomic.LoadInt32(&w.running) == 1
}
func main() {
w := &Worker{}
w.Start()
fmt.Println("Running:", w.IsRunning())
time.Sleep(200 * time.Millisecond)
fmt.Println("Running:", w.IsRunning())
}
Example 4: Options Pattern (Avoiding Bool Parameters)¶
package main
import "fmt"
// Bad: boolean parameter hell
// func CreateUser(name string, isAdmin bool, sendEmail bool, validateEmail bool) {}
// Good: options struct
type CreateUserOptions struct {
IsAdmin bool
SendEmail bool
ValidateEmail bool
}
type User struct {
Name string
IsAdmin bool
}
func CreateUser(name string, opts CreateUserOptions) (*User, error) {
if opts.ValidateEmail {
// validate...
}
u := &User{Name: name, IsAdmin: opts.IsAdmin}
if opts.SendEmail {
fmt.Printf("Sending welcome email to %s\n", name)
}
return u, nil
}
func main() {
u, _ := CreateUser("Alice", CreateUserOptions{
IsAdmin: true,
SendEmail: true,
ValidateEmail: false,
})
fmt.Printf("Created: %+v\n", u)
}
Example 5: Circuit Breaker with Bool¶
package main
import (
"fmt"
"time"
)
type CircuitBreaker struct {
isOpen bool
failCount int
threshold int
resetAfter time.Duration
openedAt time.Time
}
func NewCircuitBreaker(threshold int, resetAfter time.Duration) *CircuitBreaker {
return &CircuitBreaker{
threshold: threshold,
resetAfter: resetAfter,
}
}
func (cb *CircuitBreaker) CanExecute() bool {
if !cb.isOpen {
return true
}
// Check if reset time has passed
if time.Since(cb.openedAt) > cb.resetAfter {
cb.isOpen = false
cb.failCount = 0
return true
}
return false
}
func (cb *CircuitBreaker) RecordFailure() {
cb.failCount++
if cb.failCount >= cb.threshold {
cb.isOpen = true
cb.openedAt = time.Now()
fmt.Println("Circuit breaker OPEN")
}
}
func main() {
cb := NewCircuitBreaker(3, 5*time.Second)
for i := 0; i < 5; i++ {
if cb.CanExecute() {
fmt.Printf("Call %d: executing\n", i+1)
cb.RecordFailure() // simulate failure
} else {
fmt.Printf("Call %d: rejected (circuit open)\n", i+1)
}
}
}
Coding Patterns¶
Pattern 1: Functional Options with Bool¶
type ServerConfig struct {
TLS bool
Logging bool
Debug bool
}
type Option func(*ServerConfig)
func WithTLS() Option { return func(c *ServerConfig) { c.TLS = true } }
func WithLogging() Option { return func(c *ServerConfig) { c.Logging = true } }
func WithDebug() Option { return func(c *ServerConfig) { c.Debug = true } }
func NewServer(opts ...Option) *ServerConfig {
c := &ServerConfig{} // all false by default
for _, o := range opts {
o(c)
}
return c
}
Pattern 2: Lazy Boolean with Function¶
// Expensive computation only when needed
type LazyBool struct {
computed bool
value bool
compute func() bool
}
func (l *LazyBool) Get() bool {
if !l.computed {
l.value = l.compute()
l.computed = true
}
return l.value
}
Pattern 3: All/Any Helpers¶
func All(predicates ...bool) bool {
for _, p := range predicates {
if !p {
return false
}
}
return true
}
func Any(predicates ...bool) bool {
for _, p := range predicates {
if p {
return true
}
}
return false
}
Clean Code¶
Avoid boolean parameters (boolean blindness):
// Hard to read
processOrder(order, true, false, true)
// Clear
processOrder(order, ProcessOptions{
Validate: true,
Notify: false,
Archive: true,
})
Extract complex boolean logic:
// Hard to read
if user.Age >= 18 && user.Country == "US" && !user.IsBanned && user.SubscriptionLevel > 0 {
}
// Clear
if canAccessPremiumContent(user) {
}
func canAccessPremiumContent(u User) bool {
return u.Age >= 18 &&
u.Country == "US" &&
!u.IsBanned &&
u.SubscriptionLevel > 0
}
Product Use / Feature¶
Real-World Feature Flag System:
Companies like LinkedIn, Etsy, and Flickr pioneered feature flags. Modern systems like LaunchDarkly serve billions of bool evaluations per day.
type FeatureFlag struct {
Name string
Enabled bool
RolloutPct float64 // 0.0 to 1.0
AllowedUsers map[string]bool
}
type FlagService struct {
flags map[string]FeatureFlag
}
func (fs *FlagService) IsEnabled(flagName, userID string) bool {
flag, ok := fs.flags[flagName]
if !ok {
return false // safe default
}
if !flag.Enabled {
return false
}
// Per-user override
if flag.AllowedUsers[userID] {
return true
}
// Percentage rollout using hash
// (simplified: use actual hash of userID in production)
return flag.RolloutPct >= 1.0
}
Error Handling¶
package main
import (
"errors"
"fmt"
)
var (
ErrNotFound = errors.New("not found")
ErrPermission = errors.New("permission denied")
)
type Result struct {
Value interface{}
Success bool
Error error
}
func fetchUser(id int) Result {
if id <= 0 {
return Result{Success: false, Error: ErrNotFound}
}
return Result{Value: "Alice", Success: true}
}
func main() {
r := fetchUser(1)
if !r.Success {
fmt.Println("Error:", r.Error)
return
}
fmt.Println("User:", r.Value)
}
Security Considerations¶
Boolean as Access Control — Dangerous Pattern¶
// DANGEROUS: single bool controls admin access
type Session struct {
UserID int
IsAdmin bool // If JWT is tampered or session is hijacked, attacker gains admin
}
// BETTER: check permissions at the database/service level
type Session struct {
UserID string
Token string
}
func (s Session) HasPermission(perm string) bool {
// Fetch from DB or token claims, don't store in session
return permissionService.Check(s.UserID, perm)
}
Timing Attacks with Bool¶
// VULNERABLE: timing attack possible (early return leaks information)
func checkPassword(input, stored string) bool {
if len(input) != len(stored) {
return false // faster return leaks length info
}
for i := range input {
if input[i] != stored[i] {
return false // early return leaks position info
}
}
return true
}
// SAFE: constant-time comparison
import "crypto/subtle"
func checkPassword(input, stored []byte) bool {
return subtle.ConstantTimeCompare(input, stored) == 1
}
Performance Tips¶
Put Cheapest Check First in &&¶
// Slow: always calls len() AND regex, even for empty strings
if regexp.MustCompile(`^[a-z]+$`).MatchString(s) && len(s) > 0 {
}
// Fast: len() is O(1), regex is expensive
if len(s) > 0 && regexp.MustCompile(`^[a-z]+$`).MatchString(s) {
}
map[string]struct{} vs map[string]bool for Sets¶
// map[string]bool: 1 byte per entry for value
visited := make(map[string]bool)
visited["page1"] = true
// map[string]struct{}: 0 bytes per entry for value (empty struct)
visited := make(map[string]struct{})
visited["page1"] = struct{}{}
_, exists := visited["page1"]
For large sets (thousands of entries), struct{} saves memory.
Metrics & Analytics¶
type ABTestResult struct {
Variant string
Converted bool
ClickedCTA bool
Bounced bool
}
func ConversionRate(results []ABTestResult) float64 {
if len(results) == 0 {
return 0
}
converted := 0
for _, r := range results {
if r.Converted {
converted++
}
}
return float64(converted) / float64(len(results))
}
Best Practices¶
- Boolean blindness: Use options structs instead of bool params
- Prefer positive naming:
isEnabledoverisNotDisabled - Extract complex boolean expressions into named functions
- Use
okidiom for map lookups and type assertions - Concurrency: use
sync/atomicorsync.Mutexfor shared booleans - State machines beat boolean explosion (>3 related booleans)
- Short-circuit order: cheapest checks first, side-effectful checks last
- Default to false: design structs so
falseis safe
Edge Cases & Pitfalls¶
Pitfall 1: State Explosion¶
// 4 booleans = 16 possible states
// Many are likely invalid/impossible
type Package struct {
IsPacked bool
IsShipped bool
IsDelivered bool
IsReturned bool
}
// Can be IsPacked=false AND IsDelivered=true? That's invalid!
// Use a state type instead.
Pitfall 2: Race Condition¶
var done bool
go func() {
time.Sleep(100 * time.Millisecond)
done = true // DATA RACE: unsynchronized write
}()
for !done { // DATA RACE: unsynchronized read
time.Sleep(10 * time.Millisecond)
}
Fix with channel or atomic.
Pitfall 3: Shadowed ok¶
ok := true
if val, ok := someMap[key]; ok { // 'ok' here is a NEW variable
_ = val
}
// outer 'ok' is still true, not affected
Common Mistakes¶
1. Not Using Short-Circuit for Nil Safety¶
// PANIC if ptr is nil
if ptr.field > 0 && ptr != nil { }
// SAFE: check nil first
if ptr != nil && ptr.field > 0 { }
2. Boolean Struct Field with Misleading Zero Value¶
type Config struct {
EnableCache bool // zero value = false = cache disabled
// But what if enabling cache is the desired default?
}
// Better: make "enabled" the explicit case, or use a pointer
3. Using Bool as Bitfield (C-style)¶
// Don't try to pack booleans manually in Go
flags := 0
flags |= 1 << 0 // bit 0 = isActive
flags |= 1 << 1 // bit 1 = isPaid
// This is error-prone and unreadable. Use a struct with bool fields.
Common Misconceptions¶
"Short-circuit means only one side is evaluated": Both sides can be evaluated; short-circuit means evaluation stops as soon as the result is determined. In false && expensive(), expensive() is not called. In true && expensive(), it is.
"bool is thread-safe": No. Concurrent reads and writes to a bool are a data race without synchronization.
"You can convert bool to int in Go": You cannot with a simple cast. You need explicit logic: if b { return 1 }; return 0.
Tricky Points¶
Operator Precedence (Full Table)¶
So a || b && c = a || (b && c) — && binds tighter.
Boolean Short-Circuit with Side Effects¶
count := 0
inc := func() bool { count++; return count < 3 }
// count will be incremented until false
for inc() && inc() {
}
fmt.Println(count) // depends on short-circuit behavior
Demorgan's Laws (Useful for Simplification)¶
Test¶
package bool_test
import (
"testing"
)
func TestAll(t *testing.T) {
tests := []struct {
name string
args []bool
expected bool
}{
{"all true", []bool{true, true, true}, true},
{"one false", []bool{true, false, true}, false},
{"empty", []bool{}, true}, // vacuous truth
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := All(tt.args...); got != tt.expected {
t.Errorf("All(%v) = %v, want %v", tt.args, got, tt.expected)
}
})
}
}
func All(preds ...bool) bool {
for _, p := range preds {
if !p { return false }
}
return true
}
func TestShortCircuit(t *testing.T) {
called := false
sideEffect := func() bool {
called = true
return true
}
_ = false && sideEffect()
if called {
t.Error("sideEffect should not have been called with short-circuit")
}
}
Tricky Questions¶
Q1: What is false && sideEffect() guaranteed to do? A: sideEffect() is guaranteed NOT to be called. Short-circuit evaluation is defined behavior in Go.
Q2: Are bool variables safe to share across goroutines? A: No. They are not atomic. Use sync/atomic with int32 or sync.Mutex.
Q3: What does !(a && b) equal? A: !a || !b — De Morgan's law.
Q4: When should you use a state type instead of multiple booleans? A: When you have 3+ related boolean fields that represent mutually exclusive states.
Q5: What's wrong with func Process(data string, validate bool, cache bool)? A: Boolean blindness — callers write Process(data, true, false) which is unreadable. Use an options struct.
Cheat Sheet¶
Type: bool
Values: true / false
Zero value: false (safe default)
Size: 1 byte
Idiomatic patterns:
val, ok := map[key] // map lookup
val, ok := iface.(Type) // type assertion
v, ok := <-ch // channel receive
Concurrency:
var flag int32 // NOT bool
atomic.StoreInt32(&flag, 1)
atomic.LoadInt32(&flag)
Anti-patterns:
func f(a, b, c bool) // use options struct
!(a && b) // use !a || !b (clearer)
multiple booleans for state // use state type/iota
De Morgan's:
!(A && B) == !A || !B
!(A || B) == !A && !B
Self-Assessment Checklist¶
- I understand why Go forbids implicit bool conversions
- I use the
okidiom for map lookups and type assertions - I know when to use options structs instead of bool parameters
- I understand state explosion and when to use iota instead
- I know booleans are NOT thread-safe
- I can apply De Morgan's laws to simplify logic
- I put cheaper checks before expensive ones in
&&/|| - I understand
map[string]struct{}vsmap[string]boolfor sets
Summary¶
At the middle level, bool is about design decisions: - When is bool the right type vs. an enum/state type? - Boolean blindness is a real code smell — avoid bool parameters - Concurrency requires atomic or mutex-protected access - Short-circuit is not just a trick — it's a design tool - State explosion is a warning sign that you need a different abstraction
Well-designed boolean usage leads to more readable, safer, and more maintainable code.
What You Can Build¶
- Feature flag service with per-user and percentage rollouts
- Validation pipeline returning detailed errors alongside bool results
- Circuit breaker using atomic bool to protect external services
- Permission system with
map[string]boolper user - A/B testing framework with bool-based cohort assignment
- Configuration loader with functional options pattern
Further Reading¶
- Go Blog: The Go Memory Model
- Go Blog: Share Memory By Communicating
- Effective Go: Data
- Clean Code: Function Arguments (Robert C. Martin)
- Feature Flags: Martin Fowler
Related Topics¶
- iota and constants — for multi-state enumeration
- sync/atomic — thread-safe bool-like operations
- channels — idiomatic Go signaling (
done chan struct{}) - interfaces — type assertions use the
okidiom - error handling —
val, errvsval, boolpatterns
Diagrams & Visual Aids¶
State Machine vs. Multiple Booleans¶
Multiple Booleans (messy):
IsPending | IsPaid | IsShipped | IsDelivered
-----------|--------|-----------|------------
true | false | false | false ← valid
false | true | false | false ← valid
false | true | true | false ← valid
true | true | true | false ← INVALID! (paid AND pending?)
...32 combinations total, many invalid
State Machine (clean):
Pending → Paid → Shipped → Delivered
↘ Cancelled
Short-Circuit Decision Tree¶
A && B:
Evaluate A
A = false → Result = false (skip B)
A = true → Evaluate B → Result = B
A || B:
Evaluate A
A = true → Result = true (skip B)
A = false → Evaluate B → Result = B
Evolution & Historical Context¶
The bool type as a distinct type was introduced by C++ (C used int). Java had boolean. Go's strict no-implicit-conversion rule was a deliberate design choice to avoid C's notorious if (x = 0) bug family.
Go's ok idiom (returning (T, bool)) was inspired by the need to distinguish "key not found" from "key found with zero value" in maps — a problem that C++ solved awkwardly with map::find().
Alternative Approaches¶
| Approach | When to Use |
|---|---|
bool field | Simple two-state: enabled/disabled |
iota enum | 3+ mutually exclusive states |
map[string]bool | Dynamic set of permissions/features |
chan struct{} | Goroutine signaling |
sync/atomic int32 | Thread-safe bool flag |
| Bitmask | Many flags, memory-critical systems |
Anti-Patterns¶
- Boolean parameter —
func create(validate bool)— use options struct - Multiple bool fields for related state — use iota
- Unsynchronized bool in goroutines — use atomic
- Double negation —
!isNotActive— rename - Returning bool instead of error — for functions that can fail, return
error if flag == true— redundant comparison
Debugging Guide¶
Problem: Bool never changes in goroutine
// PROBLEM: compiler may cache the value in a register
done := false
go func() { done = true }()
for !done { } // may loop forever
// FIX: use channel or atomic
Problem: Short-circuit masking bug
// If validate() has a side effect and is being skipped
if len(items) > 0 && validate(items) {
}
// DEBUG: temporarily remove short-circuit
valid := validate(items) // force evaluation
if len(items) > 0 && valid {
}
Problem: Zero value confusion
type Config struct {
DisableCache bool // zero value = false = cache enabled (confusing!)
}
// FIX: rename to EnableCache (zero value = false = cache disabled)
Comparison with Other Languages¶
| Language | if 1 { } | Null/nil as false | Bool size | Atomic bool |
|---|---|---|---|---|
| Go | No (error) | No | 1 byte | No native (use int32) |
| C | Yes | Yes | 1 byte | _Atomic bool |
| Python | Yes | Yes (None=False) | ~28 bytes | threading.Event |
| Java | No | No (NPE) | 1 byte | AtomicBoolean |
| Rust | No | No | 1 byte | AtomicBool |
| JavaScript | Yes | Yes | varies | N/A (single-threaded) |
Go is unique in having no atomic bool — idiomatic Go uses channels or int32 with sync/atomic.