Go
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Functions and Methods
-
Section 4: Concurrency
-
Section 5: Packages and Tooling
-
Section 6: More Standard Library
-
Section 7: Building Services
-
Section 8: Advanced Go
-
Section 9: Go in the Cloud-Native Ecosystem
-
Section 10: Data Structures and Algorithms in Go
-
Section 11: Testing and Deployment
-
Section 12: Practical Projects
-
Section 13: More Standard Library Practice
-
Section 14: More Practice Projects
-
Section 15: Design Patterns in Go
-
Section 16: Interview Practice
-
Section 17: Package fmt In Depth
-
Section 18: Package strings and strconv
-
Section 19: Package os and io
-
Section 20: Package time
-
Section 21: Package sort and container
-
Section 22: Package encoding
-
Section 23: Package net/http In Depth
-
Section 24: Package context
-
Section 25: Package regexp and bytes
-
Section 26: Package errors In Depth
-
Section 27: Package crypto and hash
-
Section 28: Package flag and log
-
Section 29: Package sync In Depth
-
Section 30: More Practice Exercises
-
Section 31: Go Modules and Workspaces In Depth
-
Section 32: Generics Deep Dive (Go 1.18+)
-
Section 33: Testing Package In Depth
-
Section 34: More Interview and Whiteboard Practice
-
Section 35: Package math and unicode
-
Section 36: Package path and filepath
-
Section 37: Package database/sql
-
Section 38: Package text/template and html/template
-
Section 39: Package archive and compress
-
Section 40: Lower-Level net Package
-
Section 41: Package runtime and reflect
-
Section 42: Package embed and io/fs
-
Section 43: Go Assembly and CGO Basics
-
Section 44: Building CLIs and TUIs
-
Section 45: Go Performance Tuning
-
Section 46: More Real-World Projects
-
Section 47: Go in Production
-
Section 48: Go Design Patterns
-
Section 49: Go Interfaces Deep Dive
-
Section 50: Final Practice and Review
169: Semantic Import Versioning Revisited
I've seen this happen to plenty of developers who are used to how NPM or Cargo handle versions. They assume that once they run a "get" or "update" command, the project is now using the new version of the library. In Go, specifically with major versions, that's not how it works. If you don't change the import path, you're often still talking to the old version of the code, even if your go.mod says something different.
The phantom v1 dependency
Imagine you're using a hypothetical library called github.com/rob/calculator. You've been using v1 for months. The author releases v2, which changes the Add function from taking int to taking float64 to support decimals. You want that feature, so you run go get github.com/rob/calculator@v2.0.0.
Your go.mod now looks like this:
module my-app
go 1.21
require github.com/rob/calculator v2.0.0
But when you try to use the new functionality in your code, you hit a wall:
package main
import (
"fmt"
"github.com/rob/calculator" // Note the path here
)
func main() {
// This should work in v2, but the compiler is screaming
result := calculator.Add(10.5, 20.7)
fmt.Println(result)
}
The compiler gives you a confusing error: cannot use 10.5 (untyped float constant) as int value in argument to calculator.Add. You're thinking, "I just updated to v2! Why is it still expecting an integer?"
The reason is that Go treats github.com/rob/calculator and github.com/rob/calculator/v2 as two entirely different packages. By keeping the import path as the base URL, you've told Go you want the v1 (or v0) compatible version. Because of how Go's Minimal Version Selection (MVS) works, it's essentially ignoring the v2 code you downloaded because your source code isn't asking for it by name.
Explicitly versioning the import path
To actually use v2, you have to change the import path in every single file where that package is used. This is the "Semantic Import Versioning" rule: any major version from v2 onwards must include the version suffix in the path.
Here is the fix. We change the import to include /v2:
package main
import (
"fmt"
"github.com/rob/calculator/v2" // Added /v2 here
)
func main() {
// Now the compiler sees the v2 signature: Add(float64, float64)
result := calculator.Add(10.5, 20.7)
fmt.Println(result)
}
Once you change that import, you'll likely need to run go mod tidy. Go will realize that you are now explicitly requesting the v2 module, and it will align your go.mod and your imports.
I know it feels redundant to have the version in both the go.mod and the import statement, but this is a deliberate design choice. It allows a single binary to actually import both v1 and v2 of the same library simultaneously if you're in the middle of a complex migration. You can't do that if the import path is the same; the namespace would collide. By making the version part of the path, Go turns a versioning conflict into a simple naming difference.
📋 Practical Task
Migrating a Payment Processor to v3
You are maintaining a project that uses a payment library: github.com/finance/paygate. Currently, the project is using v2, and the code uses a function paygate.Process(amount int). The library has just been updated to v3, which introduces paygate.Process(amount float64, currency string).
Your Task:
- Assume your
go.modhas already been updated torequire github.com/finance/paygate v3.0.0. - Below is a broken
main.gofile. Modify the import path and the function call to correctly utilize the v3 version of the library.
package main
import (
"fmt"
"github.com/finance/paygate"
)
func main() {
// This needs to be updated for v3: Process(float64, string)
err := paygate.Process(100)
if err != nil {
fmt.Println("Payment failed")
}
}
There are no comments for now.