Phát hành Go 1.22
(go.dev)- Biến vòng lặp
forđược thay đổi để được tạo mới ở mỗi lần lặp, nhằm tránh các lỗi chia sẻ ngoài ý muốn; cũng có thể dùngrangevới số nguyên - Tính tương thích vẫn duy trì cam kết tương thích Go 1, và kỳ vọng gần như mọi chương trình Go sẽ biên dịch và chạy như trước
- Có thể dùng thư mục vendor được tạo bằng
go work vendortrong workspace; nếu workspace cóvendorthì-mod=vendorsẽ là giá trị mặc định - Ngừng hỗ trợ
go getbên ngoài module trong chế độ legacyGOPATHkhiGO111MODULE=off; các lệnh build khác nhưgo buildvàgo testvẫn tiếp tục hoạt động với các chương trình legacyGOPATH - Runtime được thay đổi để đặt metadata GC dựa trên kiểu gần các đối tượng heap, giúp cải thiện hiệu năng CPU của chương trình Go 1–3% và giảm overhead bộ nhớ của nhiều chương trình khoảng 1%
- Căn chỉnh địa chỉ của một số đối tượng có thể đổi từ mức 16 byte trở lên trước đây sang căn chỉnh 8 byte; mã assembly giả định mức căn chỉnh cao hơn cần được sửa, và có thể dùng
GOEXPERIMENT=noallocheadersnhư cách обход tạm thời - Build PGO có thể devirtualize nhiều lời gọi hơn; khi bật PGO trên một tập hợp các chương trình Go đại diện, hiệu năng runtime cải thiện 2–14%
- Thêm gói
v2đầu tiên vào thư viện chuẩn làmath/rand/v2, không kế thừaRead, có bộ sinh toàn cục luôn được seed ngẫu nhiên, cung cấp các sourceChaCha8vàPCG, cùng hàm genericN - Pattern định tuyến của
net/http.ServeMuxhỗ trợ method và wildcard; có một số phá vỡ tương thích ngược nhỏ với các pattern chứa{}và xử lý đường dẫn đã escape, có thể khôi phục hành vi cũ bằngGODEBUG=httpmuxgo121=1 - Phiên bản tối thiểu mặc định của server
crypto/tlskhi không chỉ định được đổi thành TLS 1.2, và các cipher suite không có ECDHE trong handshake trước TLS 1.3 sẽ không được cung cấp mặc định - Execution tracer được cải tổ toàn diện, sử dụng đồng hồ OS trên hầu hết nền tảng, cung cấp trace được phân đoạn có thể xử lý dạng streaming, toàn bộ duration của syscall, và thông tin OS thread mà goroutine đã chạy trên đó
- Web UI của công cụ
traceđược cập nhật như một phần của công việc hỗ trợ tracer mới, hỗ trợ hiển thị thread-oriented view và toàn bộ duration của syscall trong trace của các chương trình build bằng Go 1.22 trở lên vetđiều chỉnh cảnh báo loop closure theo ngữ nghĩa biến vòng lặp của Go 1.22, đồng thời thêm cảnh báo choappendkhông dùng giá trị trả về,time.Sincekhông bị trì hoãn trongdefer, và không khớp key/value củalog/slog- Bootstrap yêu cầu bản point release cuối cùng của Go 1.20 trở lên; dự kiến Go 1.24 sẽ yêu cầu bản point release cuối cùng của Go 1.22 trở lên
- Trên
darwin/amd64, Go toolchain mặc định tạo PIE; Go 1.22 là bản phát hành cuối cùng chạy trên macOS 10.15 Catalina, còn Go 1.23 sẽ cần macOS 11 Big Sur trở lên - Thêm kiểu
Aliasvà hàmUnaliasvàogo/types; vớigotypesalias=1, client phải xử lý kiểuAlias slices.Delete,DeleteFunc,Compact,CompactFunc,Replacesẽ zero các phần tử trong vùng bị thu nhỏ, vàInsertluôn panic nếuinằm ngoài phạm vi- Các hàm đọc thư mục trên Windows đọc mục theo lô để giảm số lần gọi hệ thống, cải thiện hiệu năng tối đa 30%
1 bình luận
Ý kiến trên Hacker News
Trong 3 năm qua tôi chủ yếu dùng TypeScript, gần đây mới bắt đầu dùng Go; ban đầu tôi hơi lo vì thiếu các hàm cho mảng hoặc hệ thống kiểu kém linh hoạt hơn
Nhưng khi thử thiết lập lại một dự án TypeScript cho đội 4–5 người, tôi mất quá nhiều thời gian vào linting, chọn thư viện định tuyến server, chọn server, tiêu chuẩn coding, xử lý lỗi cơ bản, ép dùng lỗi tùy chỉnh hoặc kiểu
Resultđể tránh địa ngụctry/catchlồng nhau, test và mocking, cấu hình Prisma, v.v.Dù tôi thích TypeScript, dùng một ngôn ngữ có thư viện chuẩn tuyệt vời, type safety đúng nghĩa và tiêu chuẩn coding tích hợp sẵn thật sự rất tiện. Go cũng có những quirks riêng nhưng khá ổn, và việc định tuyến cũng được đưa vào thư viện chuẩn nghĩa là bớt thêm một thứ phải bận tâm, nên rất tốt. Giờ tôi chỉ còn chờ các hàm
map/filter/findcho sliceTypeScript qua mỗi bản phát hành lại ngày càng phức tạp hơn cho power user, trong khi Go bổ sung những thứ còn thiếu như
rangetrên số nguyên để khiến việc sử dụng đơn giản hơnĐiều này giống như phần tiếp theo của một game cứ tiếp tục tăng thêm thiết lập và cơ chế rồi cuối cùng cần reset vì người dùng mới; với ngôn ngữ lập trình thì làm vậy khó hơn, nên thật tốt khi Go ý thức được điều này
go:embedvàgo:generatelần nào nhìn thấy ở dự án khác cũng đều gây ấn tượngCác gói
golang.org/xcũng rất hay vì gần như mọi triển khai liên quan đến RFC Internet đều đã sẵn sàng để dùng ngaymap/filter/findcho slice xuất hiện, có thể dùng thư viện lo lấy cảm hứng từ lodashĐây là bộ công cụ đa năng tôi rất thích dùng trong các dự án Go: https://github.com/samber/lo
Cứ mỗi khi đọc tài liệu để làm gì đó, chẳng mấy chốc lại có ai đó chỉ cho một mẹo dùng được nhưng không có trong tài liệu; chuyện đó lặp đi lặp lại
Nếu ghi chú phát hành chính thức hơi khô khan, tôi đã làm một phiên bản tương tác: https://antonz.org/go-1-22
forban đầu tôi không hiểu ngay, nhưng tự chạy rồi thấy kết quả ngoài dự đoán thì rất hữu íchCompactvàReplacehơi khó hiểu, tôi không chắc như vậy có đúng khôngTôi đã dùng Go hơn 9 năm, và 4 năm gần đây dùng Dart khá nhiều vì Flutter; tôi thấy hai ngôn ngữ này có thái độ với độ phức tạp hoàn toàn trái ngược. Dart cố thêm mọi tính năng có thể, còn Go thì ngược lại
Thứ nhất, trong Dart có quá nhiều cách khởi tạo giá trị bên trong class, khiến tốn rất nhiều thời gian. Tùy theo các tiền tố
final/const/static/late, vị trí khởi tạo lại khác nhau giữa constructor, factory,initState()của FlutterStatefulWidget, v.v.; khi refactor thì phải sửa khá nhiều phần khởi tạo. Getter khiến hàm trông như biến cũng làm tăng thêm sự rối rắm trong codebase mớiThứ hai, Dart có nhiều tính năng rất tiện để nhồi map, stream, v.v. vào một dòng, nên rất dễ muốn nhanh chóng tạo ra code một dòng khó hiểu. Nhưng khi debug, hoặc khi lập trình viên junior gặp các thủ thuật như
firstWhereOrNullvà tham số tùy chọn nhưorElse: () => null, họ sẽ rất bối rối. Null safety khó hiểu hoặc lỗi kiểu cũng nhiều lúc làm mọi thứ khựng lại, và khi đổi sang vòng lặpforkiểu Go đơn giản thì cảm giác thật sự nhẹ nhõminitStatecho những việc không liên quan trực tiếp đến UI. Ví dụ nếu không chỉ là đặt hint cho text field thì tôi thường tránhPhần lớn các trang Flutter hoặc có rất ít việc phải làm, hoặc parent tạo một đối tượng
MyPageController, khởi tạo theo cách mong muốn rồi để trang con hoạt động theo controller đó. Chẳng hạn ở trang danh sách, khi người dùng muốn chỉnh sửa một mục, parent tạo controller, đưa mục vào đó và gửi sang trang con; sau khi quay lại, parent có thể xem mục hoặc biến/callback của controller để quyết định thay đổi UILàm vậy có thể kiểm soát chi tiết tương tác giữa các widget mà không cần đụng đến những thứ như
InheritedWidget. Tất nhiên dùng cùng thư viện quản lý trạng thái thì tốt, nhưng thực tế cũng có nhiều trường hợp không dùngrangecũng vậy; rốt cuộc số ký tự phải gõ cũng tương tự comprehension kiểuforEach(f), chỉ là nó trải ra ba dòng nên trông dài hơn, còn tự viết tường minh thì chẳng có gì ghê gớmTuy nhiên, về khởi tạo, tôi vẫn muốn Go có một cách idiomatic để khởi tạo field của struct bằng một giá trị cụ thể. Không có constructor thì cũng ổn, nhưng đôi khi tôi muốn
boolmặc định được khởi tạo làtrueinitStatelà override củaStatefulWidget, không khác gì sự kiện vòng đời trong các framework khác, và là khái niệm phía Flutter nên không phải thứ dễ nhầm với constructorCó thể sẽ bối rối không biết nên dùng constructor thường hay factory, nhưng khó có thể nói Go vượt trội chỉ vì
initStateKhi
io.Copysao chép từTCPConnsangUnixConn, nếu có thể nó sẽ dùng system callsplice(2)của Linux, và sử dụng method mớiTCPConn.WriteToVới nâng cấp interface, zero-copy I/O lại được mở rộng một cách trong suốt thêm một lần nữa. Hiệu quả mà package
iotạo ra bằng pattern này thật sự rất lớnDù vậy, nó vẫn nằm trong phạm vi kỳ vọng
Các mẫu định tuyến được cải thiện là một nâng cấp tốt trong bối cảnh tương lai của Gorilla Mux đang bất ổn
Tuy nhiên thay đổi này phá vỡ tương thích ngược theo những cách nhỏ: có phần rõ ràng như hành vi của các pattern có
{và}thay đổi, và cũng có phần kém rõ ràng hơn như cách xử lý đường dẫn đã escape. Việc này được điều khiển bằng trườnghttpmuxgo121củaGODEBUG; đặthttpmuxgo121=1sẽ quay lại hành vi cũNhưng tôi thắc mắc vì sao điều này không xung đột với cam kết tương thích Go 1 rằng “các chương trình được viết theo đặc tả Go 1 sẽ tiếp tục biên dịch và chạy đúng mà không cần sửa đổi trong suốt vòng đời của đặc tả đó”
gotronggo.modNếu là 1.22 trở lên thì nhận hành vi thư viện mới, nếu không thì nhận hành vi cũ. Thực tế nó hoạt động tốt đến đâu thì còn phải chờ xem
{và}trong đường dẫn HTTP handlerHọ cũng cung cấp cách tắt hành vi mới nên không bắt buộc phải sửa code, dù vẫn phải đặt biến môi trường mới
Thay đổi ngữ nghĩa vòng lặp
fortrong bản phát hành này cũng là ví dụ tương tự, thực chất là một thay đổi gây phá vỡ. Dù vậy mọi chương trình Go vẫn tiếp tục biên dịch và chạy, chỉ là một số hành vi hơi khác đi. Tôi nghĩ Go đã chọn cách tiếp cận thực dụng, và đó là một trong những lý do nó thành cônggo.modđể phá vỡ theo từng phiên bản thì ít nhất cũng nên phá vỡ theo cách tốt hơnKhi đổi
go.modsang 1.22, để code biên dịch thất bại hẳn còn tốt hơn nhiều so với việc phải truy tìm các vấn đề runtime tinh viTrong Go 1.22, mỗi lần lặp của vòng lặp sẽ tạo một biến mới
Nội dung này trước đây cũng đã được thảo luận ở đây: https://news.ycombinator.com/item?id=33160236 - Go: Redefining For Loop Variable Semantics (2022)
Có thể là tôi cổ hủ, nhưng tôi không thích việc thêm
rangetrên hàm. Sự tiện lợi không đủ lớn để biện minh cho độ phức tạp được thêm vào ngôn ngữ, và phong cách functional có vẻ lệch khỏi phong cách rõ ràng, imperative, dài dòng nhưng ít tính năng của Go. Tôi nghĩ đó từng là một trong những điểm mạnh lớn của GoVì cùng lý do, tôi cũng nghĩ
rangetrên số nguyên là đi sai hướng. Tập tính năng mỏng và tải nhận thức thấp của Go luôn là điểm mạnh chính và yếu tố khác biệtMột trong những vấn đề lớn trong phát triển phần mềm là ngăn lập trình viên thêm độ phức tạp hoặc phô diễn kỹ năng. Đặc biệt là lập trình viên giai đoạn đầu sự nghiệp thường có xu hướng làm giải pháp phức tạp quá mức, thay vì giải quyết vấn đề theo cách thẳng thắn nhưng kém thanh lịch và dài dòng hơn rồi đi tiếp
Hỗ trợ generics hiện tại của Go chưa đủ tinh vi để nâng đỡ việc này, nên sinh ra những giới hạn bị ép khá kỳ lạ
Việc thêm
sql.Null[T]là tốt. Có lẽ tôi sẽ bắt đầu dùng trong các dự án mớiTrong công việc hiện tại, tôi đang phụ thuộc vào null [0] của sqlboiler, có API rất giống. Nó hoạt động như
sql.Null, nhưng có thêm phương thứcIsSet() boolcho biết giá trị đã từng được đặt rõ ràng hay chưa, giúp phân biệt giữa “null có chủ ý” và “null vì chưa khởi tạo”. Nghe có vẻ hay, nhưng thực tế tôi chưa từng dùng đếnTôi đang mong xem lại mux của thư viện chuẩn và hy vọng có thể loại bỏ chi. Thật sự rất thích khi những tính năng như thế này đi vào thư viện chuẩn
Khi chức năng như thế này đi vào thư viện chuẩn, nó được đảm bảo luôn được bảo trì, và đảm bảo nhiều chương trình Go dùng cùng một cách tiếp cận
Tôi tò mò là nếu dùng Go trong production thì mọi người có thể nhanh chóng chuyển sang phiên bản mới nhất không, hay bị kẹt ở các bản phát hành cũ
Nhìn các dự án công khai, có vẻ nhiều nơi tránh dùng các tính năng tương đối mới. Dù không dùng generics, tôi vẫn muốn dùng
anyđược đưa vào từ Go 1.18 thay chointerface{}cũ, và cũng từng nghe nói cái sau “idiomatic” hơnLần này hơi rắc rối một chút: đội Go đã phát hành 1.22 trước khi chúng tôi nâng mặc định lên 1.21. Giờ việc chờ gần như chỉ còn là thông lệ, và rất hiếm khi bản phát hành Go làm hỏng thứ gì đó
Ví dụ với generics, chúng tôi đã chờ khoảng 6 tháng trước khi dùng thật, để kiểm tra tính hữu ích, trải nghiệm lập trình viên, và liệu có ảnh hưởng đáng kể đến tốc độ build/test hay không
Nhưng chúng tôi cũng cung cấp ứng dụng Go cho người dùng cuối; phần này phải ở lại 1.20 vì khả năng tương thích với các OS client cũ. Do 1.21 đã có một đợt cắt khá lớn, có khả năng chúng tôi sẽ phải ở lại 1.20 trong vài năm
latestcủa Go, và bộ test khá chắc nên về cơ bản là tự động cập nhật. Nếu có gì không khớp, pipeline sẽ vỡ, rồi khi đó quyết định ghim vào phiên bản cuối cùng còn chạy được hay hoãn cập nhật cho đến khi sửa xong thay đổi gây lỗiVài năm một lần có thể lộ ra bug edge case tinh vi giữa phiên bản mới và package bên thứ ba, nhưng những bug như vậy theo định nghĩa đã lọt qua bộ test, nên kể cả nâng cấp thủ công thì khả năng cao cũng không bắt được
Nếu lo hỗ trợ compiler Go cũ, bạn có thể đặt build condition cho phiên bản cũ và định nghĩa những thứ còn thiếu. Ví dụ có thể đặt
type any = interface{}dưới// +build !go1.7