Menu

go mod init và Go Modules: go.mod, go get, go mod tidy

Cách Go modules hoạt động: tạo module bằng go mod init, chọn module path, thêm dependency bằng go get, dọn dẹp bằng go mod tidy, go.sum dùng để làm gì, và replace khi phát triển ở máy cục bộ.

Trang này có trình soạn thảo chạy trực tiếp. Sửa, chạy và xem đầu ra ngay lập tức.

Một module là một cây thư mục chứa các package Go, với một tệp go.mod ở gốc. go.mod đặt tên cho module và liệt kê phiên bản của mọi module khác mà nó phụ thuộc. Bạn tạo module bằng go mod init:

mkdir myapp
cd myapp
go mod init example.com/myapp
go: creating new go.mod: module example.com/myapp

Tệp go.mod được tạo ra:

module example.com/myapp

go 1.24.5

Vậy là đủ để build. Từ Go 1.16, mọi dự án Go đều là một module, và go build, go run . cùng go test sẽ từ chối hoạt động nếu không có module:

go: go.mod file not found in current directory or any parent directory; see 'go help modules'

Chọn module path

Module path là tiền tố của mọi đường dẫn import bên trong module. Với module example.com/myapp, một package trong thư mục internal/store được import là example.com/myapp/internal/store.

Tình huốngModule path
Code lưu trên GitHub mà người khác có thể importgithub.com/yourname/project
Code của công ty bạnyourcompany.com/project hoặc URL của repository
Một chương trình riêng hoặc bài tậpexample.com/myapp hoặc chỉ myapp
Phiên bản 2 trở lên của một module đã phát hànhgithub.com/yourname/project/v2

Với một thư viện, đường dẫn phải khớp với nơi code được lưu trữ, vì go get dùng nó để tìm repository. Với một chương trình chỉ mình bạn chạy, đường dẫn chỉ là một cái tên. Tránh dùng một từ đơn trùng với package trong thư viện chuẩn, như go mod init fmt hay go mod init strings: khi đó bản build sẽ thất bại với ambiguous import: found package fmt in multiple modules.

Chạy go mod init không kèm đối số sẽ thất bại bên ngoài cấu trúc GOPATH cũ:

go: cannot determine module path for source directory /home/ana/myapp (outside GOPATH, module path must be specified)

Hãy truyền cho nó một đường dẫn.

Thêm dependency

Viết import trước, rồi để Go tải về. Giả sử main.go import github.com/google/uuid. Build khi module chưa biết đến package này sẽ cho ra một chỉ dẫn rõ ràng:

main.go:6:2: no required module provides package github.com/google/uuid; to add it:
	go get github.com/google/uuid

Lệnh nào dưới đây cũng khắc phục được:

go get github.com/google/uuid
# or, to sync go.mod with every import in the module:
go mod tidy

go get báo lại những gì nó đã thay đổi:

go: downloading github.com/google/uuid v1.6.0
go: added github.com/google/uuid v1.6.0

go mod tidy báo lại cách nó giải quyết import:

go: finding module for package github.com/google/uuid
go: downloading github.com/google/uuid v1.6.0
go: found github.com/google/uuid in github.com/google/uuid v1.6.0

Giờ go.mod đã có một dòng require:

module example.com/myapp

go 1.24.5

require github.com/google/uuid v1.6.0

go get với phiên bản

LệnhTác dụng
go get pkgthêm pkg ở bản phát hành mới nhất, hoặc giữ phiên bản hiện tại nếu đã được require
go get pkg@v1.5.0dùng đúng v1.5.0 (nâng cấp hoặc hạ cấp)
go get pkg@latestchuyển sang bản phát hành mới nhất
go get pkg@abc1234dùng một commit cụ thể (được ghi lại dưới dạng pseudo-version)
go get -u ./...nâng cấp mọi dependency lên bản minor hoặc patch mới nhất
go get -u=patch ./...chỉ nâng cấp lên các bản patch mới nhất
go get pkg@nonexóa requirement
go get go@1.24nâng phiên bản Go tối thiểu của module

go get thay đổi go.mod. Lệnh này không còn build hay cài đặt chương trình nữa; từ Go 1.18, đó là việc của go install pkg@version.

Dependency gián tiếp

Những requirement được đánh dấu // indirect là các module mà code của bạn không import trực tiếp nhưng bản build lại cần. Từ Go 1.17, go.mod liệt kê mọi module cung cấp package cho bản build, nên dependency của các dependency cũng xuất hiện ở đây với dấu đánh dấu đó. go mod tidy quản lý các dấu này; bạn không tự thêm chúng.

go mod tidy

Hãy chạy go mod tidy mỗi khi bạn thêm hoặc xóa import. Lệnh này:

  • thêm requirement cho những package được import mà còn thiếu,
  • xóa những requirement không còn được import,
  • thêm các mục go.sum mà bản build cần và bỏ những mục đã cũ.

Nó xem xét mọi package trong module, bao gồm cả test và mọi tổ hợp build tag, nên đôi khi nó giữ lại một dependency mà bạn không thấy được dùng trên nền tảng của mình. Chạy go mod tidy trước mỗi lần commit, và kiểm tra trong CI rằng nó không tạo ra khác biệt nào, sẽ giúp go.mod luôn chính xác.

go.sum

go.sum ghi lại một mã băm cho mỗi phiên bản module mà bản build dùng:

github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=

Dòng đầu băm các tệp của module, dòng thứ hai chỉ băm go.mod của nó. Khi lệnh go tải một module, nó đối chiếu mã băm với go.sum và với cơ sở dữ liệu checksum công khai (sum.golang.org), nên một bản phát hành bị can thiệp hoặc âm thầm thay đổi sẽ làm bản build thất bại. Hãy commit go.sum cùng với go.mod, và đừng bao giờ sửa tay.

Cách Go chọn phiên bản

Go dùng minimal version selection (chọn phiên bản tối thiểu). Mỗi module liệt kê phiên bản tối thiểu của từng dependency mà nó cần, và bản build dùng phiên bản cao nhất trong số các mức tối thiểu đó, không bao giờ dùng bản mới hơn. Nếu module của bạn yêu cầu uuid v1.5.0 và một dependency yêu cầu uuid v1.6.0, bản build sẽ dùng v1.6.0, kể cả khi v1.7.0 đã có. Không có gì được nâng cấp trừ khi ai đó yêu cầu bằng go get.

Kết quả là các bản build có thể tái lập mà không cần tệp lock: go.mod cùng với đồ thị dependency xác định hoàn toàn mọi phiên bản. go list -m all in ra kết quả:

go list -m all
example.com/myapp
github.com/google/uuid v1.6.0

Major version

Một module từ v2 trở lên phải ghi major version vào đường dẫn: github.com/yourname/project/v2. Đường dẫn import cũng thay đổi theo, nên project và project/v2 là hai module khác nhau và có thể cùng có mặt trong một bản build. Đây là quy tắc của Go cho các thay đổi phá vỡ tương thích, và là lý do bạn thấy những import như github.com/jackc/pgx/v5.

replace: làm việc với dependency ở máy cục bộ

Để thử các thay đổi trên một dependency trước khi phát hành, hãy trỏ đường dẫn của nó tới một thư mục cục bộ:

module example.com/myapp

go 1.24.5

require example.com/mylib v1.2.0

replace example.com/mylib => ../mylib

Hoặc từ dòng lệnh:

go mod edit -replace example.com/mylib=../mylib
go mod tidy

Thư mục đó phải có go.mod riêng. replace chỉ có hiệu lực khi build trực tiếp module này, không áp dụng khi người khác phụ thuộc vào nó, nên nhớ xóa nó trước khi gắn tag phát hành.

Để sửa nhiều module cùng lúc mà không động tới các tệp go.mod của chúng, Go 1.18 đã bổ sung workspace:

go work init . ../mylib

Lệnh này ghi ra một tệp go.work khiến mylib ở máy cục bộ được ưu tiên. Đừng đưa go.work vào hệ thống quản lý phiên bản, trừ khi cả nhóm dùng cùng một cấu trúc thư mục.

Dependency cho công cụ (Go 1.24)

Go 1.24 bổ sung chỉ thị tool, để các trình sinh code và linter có thể được quản lý phiên bản trong go.mod thay vì cài đặt toàn cục:

go get -tool golang.org/x/tools/cmd/stringer
go tool stringer -type=Color

func init là chuyện khác

Những lượt tìm kiếm "golang init" thường muốn nói đến hàm init, vốn không liên quan gì đến go mod init. Package nào cũng có thể khai báo func init(). Hàm này không nhận đối số, code của bạn không thể gọi nó, và nó tự động chạy một lần, sau khi các biến cấp package được gán giá trị và trước main:

Một tệp có thể có nhiều hàm init, và chúng chạy theo thứ tự xuất hiện. Các package được import sẽ hoàn tất việc khởi tạo của chúng trước. Hãy giữ init thật nhỏ: những việc có thể thất bại sẽ dễ xử lý và kiểm thử hơn khi là một hàm bình thường được gọi từ main.

Module riêng tư và proxy

Theo mặc định, go tải module qua proxy.golang.org. Proxy đó không thấy được các repository riêng tư, nên hãy cho Go biết những đường dẫn nào là riêng tư:

go env -w GOPRIVATE=github.com/yourcompany/*

Các module khớp với GOPRIVATE được tải trực tiếp từ repository bằng thông tin xác thực git của bạn và bỏ qua cơ sở dữ liệu checksum.

Câu hỏi thường gặp

go mod init làm gì?

Lệnh này tạo một tệp go.mod trong thư mục hiện tại, biến thư mục đó thành gốc của một module. go mod init example.com/myapp ghi module path và phiên bản Go:

module example.com/myapp

go 1.24.5

Chạy lệnh này một lần cho mỗi dự án, trước go build hoặc go get.

Nên dùng module path nào trong go mod init?

Dùng địa chỉ mà code sẽ được tải về nếu người khác import nó, thường là đường dẫn repository: go mod init github.com/yourname/project. Với một chương trình không ai import, tên nào cũng được (go mod init myapp), nhưng một đường dẫn dạng tên miền có dấu chấm như example.com/myapp sẽ tránh trùng với tên package trong thư viện chuẩn.

go get và go mod tidy khác nhau thế nào?

go get pkg@version thêm một dependency hoặc đổi phiên bản của nó. go mod tidy đọc các tệp mã nguồn của bạn, thêm mọi module mà các import cần nhưng go.mod còn thiếu, xóa các requirement không còn được import, và cập nhật go.sum. Một quy trình phổ biến là viết import trước, rồi chạy go mod tidy.

Có nên commit go.sum không?

Có. go.sum chứa mã băm mật mã của mọi phiên bản module mà bản build của bạn dùng. Commit tệp này giúp lệnh go xác minh rằng mọi người đều tải về những dependency giống hệt nhau đến từng byte. Đừng bao giờ sửa tay; go mod tidy tự duy trì nó.

"golang init" có giống go mod init không?

Không, đây là hai thứ khác nhau chỉ trùng một từ. go mod init là một lệnh terminal tạo ra go.mod. func init() là một hàm bạn viết trong code Go; nó tự động chạy một lần, trước main, sau khi các biến của package đã được khởi tạo.

Hình minh họa các ngôn ngữ lập trình của Coddy

Học lập trình cùng Coddy

Bắt đầu