Go generics là gì? Lập trình tổng quát trong Golang

Go generics là tính năng lập trình tổng quát được giới thiệu trong Go 1.18, cho phép lập trình viên viết mã linh hoạt, tái sử dụng được mà vẫn giữ được kiểm tra kiểu tĩnh tại thời điểm biên dịch. Trước đây, Go chỉ có interface{} như một cách đa năng, nhưng điều này đòi hỏi type assertion và dễ gây l��i runtime. Với generics, bạn có thể định nghĩa hàm và kiểu dữ liệu hoạt động với nhiều kiểu khác nhau mà không cần lặp lại mã.

Cú pháp type parameters trong Go với generic function Max[T constraints.Ordered]

Thay vì viết riêng hàm SumInts cho int64 và SumFloats cho float64, bạn có thể viết một hàm Sum[T constraints.Number](m map[string]T) T duy nhất. Type parameter được khai báo trong cặp ngoặc vuông, sau đó dùng trong thân hàm như một kiểu bình thường. Đây là bước tiến lớn giúp mã nguồn ngắn gọn hơn và ít l��i hơn.

Tại sao Go cần generics

Go được thiết kế với triết lý đơn giản và rõ ràng, nhưng thiếu generics khiến lập trình viên phải lặp lại mã khi xử lý nhiều kiểu dữ liệu khác nhau. Trước Go 1.18, bạn thường rơi vào một trong hai tình huống:

  • Viết nhiều hàm gần giống nhau cho từng kiểu: code dài, khó bảo trì.
  • Dùng interface{} rồi ép kiểu: mất kiểm tra tĩnh, dễ gây panic khi đúng kiểu.

Generics giải quyết vấn đề này bằng cách cho phép bạn viết mã một lần, dùng với nhiều kiểu. Tính năng này đã có mặt trong nhiều ngôn ngữ như C++, Java, C# và Rust, và Go chính thức gia nhập nhóm này vào năm 2022.

Khái niệm cơ bản: type parameter và type constraint

Type parameter là tham số kiểu, tương tự tham số hàm nhưng dành cho kiểu dữ liệu. Bạn khai báo nó trong ngoặc vuông khi định nghĩa hàm hoặc struct. Ví dụ:

func Max[T constraints.Ordered](a, b T) T {
  if a > b { return a }
  return b
}

�� đây, T là type parameter, còn constraints.Ordered là type constraint. Constraint quy định T phải hỗ trợ các phép so sánh như >, <. Nếu bạn truyền kiểu không th��a constraint, trình biên dịch sẽ báo l��i ngay lập tức.

So sánh code truyền thống lặp lại hàm cho từng kiểu và code dùng generics viết một lần dùng cho nhiều kiểu

Type constraint thực chất là một interface chứa type set — tập hợp các kiểu được phép dùng. Ví dụ, bạn có thể tạo constraint riêng cho int, int64, float32, float64 hoặc thậm chí cho các kiểu do bạn tự định nghĩa.

Các constraint có sẵn trong package constraints

Go cung cấp package constraints với nhiều constraint định sẵn, giúp bạn không phải tự viết lại interface phổ biến:

  • constraints.Ordered: các kiểu hỗ trợ so sánh (int, float, string, v.v.)
  • constraints.Integer: tất cả kiểu số nguyên có dấu và không dấu
  • constraints.Float: float32 và float64
  • constraints.Complex: complex64 và complex128
  • constraints.Signed: các kiểu số nguyên có dấu
  • constraints.Unsigned: các kiểu số nguyên không dấu

Để dùng, chỉ cần import golang.org/x/exp/constraints. Ví dụ khi viết hàm tính tổng slice số:

func SumSlice[T constraints.Integer | constraints.Float](s []T) T {
  var total T
  for _, v := range s { total += v }
  return total
}

Cách này giúp code ngắn gọn mà vẫn rõ ràng về ý định.

So sánh code truyền thống và Go generics

Trước Go 1.18, nếu bạn muốn tìm phần tử lớn nhất trong một slice, bạn phải viết hàm riêng cho từng kiểu. Điều này dẫn đến mã trùng lặp và khó mở rộng.

Với generics, bạn chỉ cần viết một hàm FindMax[T constraints.Ordered](s []T) T và dùng cho mọi kiểu hỗ trợ so sánh. Điều này không chỉ tiết kiệm dòng mã mà còn giảm thiểu l��i copy-paste và đảm bảo tính nhất quán khi sửa đổi.

Khi nào nên dùng generics trong Go

Generics không phải là giải pháp cho mọi vấn đề. Bạn nên cân nhắc dùng generics trong các trường hợp sau:

  • Viết thư viện cần tái sử dụng trên nhiều kiểu: slice, map, stack, queue.
  • Khi interface{} gây quá nhiều type assertion hoặc không đảm bảo an toàn kiểu.
  • Xây dựng cấu trúc dữ liệu chung như Set[T], Tree[T], Graph[T].

Tuy nhiên, nếu bạn chỉ đang viết business logic đơn giản, interface{} hoặc composition thường vẫn là lựa chọn phù hợp. Go 1.18 cũng khuyến nghị ưu tiên interface hành vi thay vì generics khi không cần thiết.

Các giới hạn của Go generics

Go generics có một số giới hạn chủ ý để giữ ngôn ngữ đơn giản:

  • Không có method specialization: không thể viết hai version khác nhau của cùng một method cho kiểu khác nhau.
  • Không có operator overloading: bạn không thể định nghĩa toán tử +, * cho kiểu riêng.
  • Type parameter chỉ có thể xuất hiện ở cấp function hoặc type definition, không được lồng trong method receiver.
  • Không có generic methods: phương thức của struct không thể có type parameter riêng (chỉ struct ngoài cùng mới generic).

Những giới hạn này giúp trình biên dịch đơn giản và thời gian build nhanh hơn. Go tiếp tục tinh chỉnh generics qua các phiên bản 1.19, 1.20 và 1.21, nhưng triết lý “đủ dùng, không thừa” vẫn được giữ v��ng.

Tài liệu tham khảo

Tôi là một lập trình viên IOS. Code chính là IOS nhưng thỉnnh thoảng vẫn đá sang Android hoặc web. Mặc dù không quá thông thạo nhưng tôi sẽ chia sẻ những kiến thức mà mình đã tìm hiểu, áp dụng qua.

Bài viết liên quan

Rust là gì? Tổng quan ngôn ngữ lập trình hệ thống hiện đại

Rust là gì? Tổng quan ngôn ngữ lập trình hệ thống hiện đại Rust là ngôn ngữ lập trình đa mô hình hiện đại được phát triển bởi Mozilla, tập…

Xem thêm

HTMX là gì? Thư viện nhẹ cho giao diện web động không cần JavaScript framework

Trong những năm gần đây, giới phát triển web liên tục tìm kiếm cách cân bằng giữa trải nghiệm người dùng mượt mà và sự đơn giản trong bảo trì…

Xem thêm

Zig là gì? Ngôn ngữ lập trình hệ thống thay thế C

Zig là gì? Zig là ngôn ngữ lập trình hệ thống được Andrew Kelley phát triển từ năm 2015, ra mắt phiên bản ổn định 1.0 vào tháng 3 năm…

Xem thêm
0 0 đánh giá
Article Rating
Theo dõi
Thông báo của
guest
0 Comments
Cũ nhất
Mới nhất Được bỏ phiếu nhiều nhất