Why this matters
Deferring cleanup right after acquiring a resource ensures that no matter how the function exits (return or panic), the resource will be released. This prevents resource leaks and deadlocks caused by forgotten closes or unlocks.
Always use `defer` to release resources like files or locks when a function has multiple exit points. Open or lock resources and immediately defer their close/unlock to ensure they get released on every code path.
Deferring cleanup right after acquiring a resource ensures that no matter how the function exits (return or panic), the resource will be released. This prevents resource leaks and deadlocks caused by forgotten closes or unlocks.
Side-by-side examples engineers can pattern-match during review.
func ReadConfig(path string) ([]byte, error) {
f, err := os.Open(path)
if err != nil {
return nil, err
}
data, err := io.ReadAll(f)
if err != nil {
f.Close() // closes file on error path
return nil, err
}
// Forgot to close file on success path
return data, nil
}func ReadConfig(path string) ([]byte, error) {
f, err := os.Open(path)
if err != nil {
return nil, err
}
defer f.Close()
data, err := io.ReadAll(f)
if err != nil {
return nil, err
}
return data, nil
}func ReadConfig(path string) ([]byte, error) {
f, err := os.Open(path)
if err != nil {
return nil, err
}
data, err := io.ReadAll(f)
if err != nil {
f.Close() // closes file on error path
return nil, err
}
// Forgot to close file on success path
return data, nil
}func ReadConfig(path string) ([]byte, error) {
f, err := os.Open(path)
if err != nil {
return nil, err
}
defer f.Close()
data, err := io.ReadAll(f)
if err != nil {
return nil, err
}
return data, nil
}From the same buckets as this rule.
Check if loops use equality operators (== or !=) in termination conditions. These can lead to infinite loops if the condition is never met exactly. Instead, use relational operators like < or > for safer loop termination.