New FAMILUG

The PyMiers

Monday, 15 May 2023

[Go] any chứa được int nhưng []any không chứa được []int

Theo go tour

An interface type is defined as a set of method signatures. A value of interface type can hold any value that implements those methods.

Một kiểu interface định nghĩa một bộ các method. Một giá trị kiểu interface có thể chứa bất kỳ giá trị nào có đủ các method trong interface.

The interface type that specifies zero methods is known as the empty interface: An empty interface may hold values of any type. (Every type implements at least zero methods.)

Một kiểu interface không chỉ định method nào được gọi là empty interface: interface{} Một empty interface có thể chứa giá trị của bất kỳ kiểu nào.

any trong Go là gì

Từ Go phiên bản 1.18 giới thiệu cách viết khác cho interface{} là: any.

any Photo by Andrew Wulf on Unsplash

function nhận any, nhận mọi giá trị

package main

import "fmt"

func doThing(x any) {
    fmt.Print(x)
}

func main() {
    n := 5
    doThing(n)

    s := "hello"
    doThing(s)
}

function doThing nhận đầu vào kiểu any, và nó có thể gọi với mọi giá trị, n kiểu int, hay s kiểu string.

Ai viết function nhận vào kiểu any? fmt.Print là 1 ví dụ.

func Println(a ...any) (n int, err error)

function nhận []any không nhận []int

Dễ suy luận rằng 1 slice của int là []int, thì 1 function nhận []any sẽ nhận []int? Không! Code sau đây compile với error:

package main

import "fmt"

func doManyThing(xs []any) {
    for _, x := range xs {
        fmt.Printf("%s\n", x)
    }
}

func main() {
    xs := []int{1, 2, 3}
    doManyThing(xs)
}
./any.go:19:14: cannot use xs (variable of type []int) as []any value in argument to doManyThing

Muốn dùng xs làm đầu vào cho doManyThing, phải convert nó thành []any bằng 1 vòng lặp for:

xs := []int{1, 2, 3}
anyxs := make([]interface{}, len(xs))
for i, v := range xs {
    anyxs[i] = v
}

doManyThing(anyxs)

Bất ngờ chưa? đây cũng là 1 câu hỏi trong FAQ của Go, tất nhiên, vì có nhiều người hỏi quá: https://go.dev/doc/faq#convert_slice_of_interface

Q: Can I convert a []T to an []interface{}?

A: Not directly. It is disallowed by the language specification because the two
types do not have the same representation in memory. It is necessary to copy
the elements individually to the destination slice. `

Cơ mà... any thì lại chứa được []int.

Kết luận

Go thật bất ngờ.

Hết.

HVN at http://pymi.vn and https://www.familug.org.

Ủng hộ tác giả 🍺

Monday, 8 May 2023

[Go] := đôi khi không gán giá trị

Tạo variable (biến) trong Go

Go có 2 cách để khai báo và khởi tạo giá trị cho variable:

var x int // declare
x = 42 // init

có thể viết gọn lại

var x int = 42

Hoặc cú pháp ngắn "short declaration":

x := 42

Short declaration rules

Short declaration := ngắn hơn, được ưa chuộng, nhưng cũng kèm theo không ít chú ý mà ít ai để ý.

package main
import (
    "fmt"
    "os"
)
func main() {
    i, err := os.Open("/etc/passwd")
    i, err := os.Open("/etc/passwd")
    fmt.Printf("i %v err %v\n", i, err)
}

Code này không compile với error: no new variables on left side of :=, bên trái := luôn phải có variable mới. Code này compile ok:

func main() {
    i, err := os.Open("/etc/passwd")
    f, err := os.Open("/etc/passwd")
    fmt.Printf("i %v f %v err %v\n", i, f, err)
}

ở đây có var f mới, còn var err cũ được gán cho giá trị mới. Bằng chứng err cũ có thể test:

func main() {
    i, _ := os.Open("/etc/passwd")
    var err int = 42
    f, err := os.Open("/etc/passwd")
    fmt.Printf("i %v f %v err %v\n", i, f, err)
}

không compile với error: cannot use os.Open("/etc/passwd") (value of type error) as int value in assignment, err cũ có kiểu int, nên không thể gán giá trị kiểu error.

Đoạn code sau rất phổ biến trong lập trình web, tạo 1 global var DB client:

var db *gorp.DbMap

//Init ...
func Init() {
    dbinfo := fmt.Sprintf("user=%s password=%s dbname=%s sslmode=disable", os.Getenv("DB_USER"), os.Getenv("DB_PASS"), os.Getenv("DB_NAME"))
    db, err := ConnectDB(dbinfo)
    if err != nil {
        log.Fatal(err)
    }

err là var mới, còn db là var mới hay var cũ được gán giá trị?

Theo những ví dụ trên, có thể mong chờ db là var cũ được gán cho giá trị. Nhưng theo effective go:

In a := declaration a variable v may appear even if it has already been declared, provided: if v is already declared in an outer scope, the declaration will create a new variable.

db trong Init là var db mới, shadow (che mất) var db bên ngoài, db bên ngoài vẫn là nil. Để fix bug này, không dùng := mà viết:

    var err error
    db, err = ConnectDB(dbinfo)

Kết luận

Go vốn dài dòng, mà chỗ ngắn cũng toàn chú ý, cộng đồng mạng tạo hẳn 6 luật để dùng := https://stackoverflow.com/questions/17891226/difference-between-and-operators-in-go/45654233#45654233

Go thật đơn giản, ha!

Hết.

HVN at http://pymi.vn and https://www.familug.org.

Ủng hộ tác giả 🍺