Trong Go, một cấu trúc rỗng struct{}
là một cấu trúc không có trường, có vẻ như ít hữu ích, nhưng trên thực tế, nó có thể hữu ích trong một số trường hợp nhất định và trở thành một giải pháp đơn giản và hiệu quả trong mã.
Như một semaphore hoặc khóa
Vì cấu trúc rỗng không có trường nào, nên nó có thể được sử dụng một cách thuận tiện để triển khai một số hàm điều khiển đồng thời, chẳng hạn như khóa mutex, khóa đọc-ghi. Chúng ta có thể sử dụng chan struct{}
để triển khai một kênh không đệm để điều khiển truy cập đồng thời.
package main
import (
"fmt"
"sync"
)
func main() {
var wg sync.WaitGroup
var mu sync.Mutex
c := make(chan struct{}, 1)
for i := 0; i < 10; i++ {
wg.Add(1)
go func(i int) {
c <- struct{}{} // acquire lock
defer func() {
<-c // release lock
wg.Done()
}()
mu.Lock()
defer mu.Unlock()
fmt.Printf("goroutine %d: hello\n", i)
}(i)
}
wg.Wait()
}
Trong đoạn mã trên, chúng ta sử dụng một cấu trúc rỗng struct{}
để biểu diễn một semaphore. Khi một goroutine có được khóa, nó gửi một cấu trúc rỗng vào kênh để chỉ ra rằng nó đã có được khóa. Sau khi thực thi, nó nhận một cấu trúc rỗng từ kênh để giải phóng khóa.
Giá trị giữ chỗ
Trong một số trường hợp, chúng ta cần khai báo một biến hoặc tham số nhưng không thực sự cần sử dụng nó. Trong những trường hợp như vậy, chúng ta có thể sử dụng một cấu trúc rỗng làm giá trị giữ chỗ để tránh lãng phí bộ nhớ. Ví dụ: chúng ta có thể sử dụng map[string]struct{} để biểu diễn một tập hợp các cặp khóa-giá trị, trong đó một cấu trúc rỗng có thể được sử dụng để biểu diễn trường hợp chúng ta chỉ cần khóa và không cần giá trị.
package main
import "fmt"
func main() {
m := map[string]struct{}{
"apple": {},
"banana": {},
"cherry": {},
"durian": {},
"elder": {},
"fig": {},
"grape": {},
"honeydew":{},
}
for k, _ := range m {
fmt.Println(k)
}
}
Trong đoạn mã trên, chúng ta sử dụng một cấu trúc rỗng làm giá trị của map[string]struct{} để chỉ ra rằng chúng ta chỉ cần khóa và không cần giá trị. Điều này có thể tránh lãng phí bộ nhớ và cải thiện khả năng đọc của mã.
Triển khai giao diện
Trong Go, một giao diện được định nghĩa bằng cách triển khai một tập hợp các phương thức. Nếu một cấu trúc không có bất kỳ phương thức nào để triển khai nhưng cần triển khai một giao diện, một cấu trúc rỗng có thể được sử dụng làm giá trị giữ chỗ để chỉ ra rằng cấu trúc đó triển khai giao diện.
package main
import "fmt"
type myInterface interface{}
type myStruct struct{}
func (ms myStruct) String() string {
return "myStruct"
}
func main() {
var i myInterface = myStruct{}
fmt.Println(i)
}
Trong đoạn mã trên, chúng ta định nghĩa một giao diện rỗng myInterface
và một cấu trúc myStruct
với phương thức String()
trả về myStruct
. Sau đó, chúng ta triển khai myInterface
bằng cách gán myStruct{}
cho biến giao diện i
, làm cho myStruct
trở thành một kiểu triển khai myInterface
.
Trong ví dụ này, vì myInterface
là một giao diện rỗng không có phương thức nào, nên myStruct
như là một triển khai của myInterface không cần phải triển khai bất kỳ phương thức nào. Tuy nhiên, vì myStruct
triển khai phương thức String()
của giao diện fmt.Stringer
, nên chúng ta có thể in giá trị của i bằng fmt.Println
và nhận được biểu diễn chuỗi của myStruct
.
Tóm lại, cấu trúc rỗng struct{}
của Go là một giải pháp đơn giản và hiệu quả có thể hữu ích trong một số trường hợp nhất định. Nó có thể được sử dụng như một semaphore hoặc khóa, giá trị giữ chỗ và như một giá trị giữ chỗ để triển khai một số giao diện. Hơn nữa, việc sử dụng một cấu trúc rỗng có thể tránh lãng phí bộ nhớ và cải thiện khả năng đọc của mã.
The first code snippet is giving a deadlock error