前書き
golangはシンプルな言語と言われていますが、実際勉強してみて、分かりづらかった点、十分に理解しないと、間違いやすい点などがあったので、 まとめてみました。
:=, =の違い
- 初期化と再代入
-
:=
は、変数の宣言と初期化を同時
に行うことができます。var キーワード、変数名、代入演算子(=)を1つの簡潔な形式で書けます -
=
は、すでに宣言されている変数の再代入
に使用されます
-
- スコープ
-
:=
は、関数またはブロック内のみ使用できます -
=
は、コードのどこでも使用できます
-
- シャドーイング
(同名の新しい変数が宣言されるときに、発生)-
:=
は、ブロック内で既存のブロック外の変数をシャドーイング(新しい変数として扱う)します -
=
は、既存の変数のスコープに影響を与えず、シャドーイングが発生しない
シャドーイング例var a = 1 var b = 1 var c = 1 func shadow1() int { a := 2 // topレベルのaと関係ない、新しいlocalのaを宣言 return a } func unshadow() { b = 2 // topレベルのbを上書きする } func shadow2() int { var c int // topレベルのcと関係ない、新しいlocalのcを宣言 c = 2 return c } func main() { shadow1() unshadow() shadow2() fmt.Println(a) // 1 fmt.Println(b) // topレベルのbが上書きされてる fmt.Println(c) // 1 }
-
関数のparameterはtypeによってpassed by reference、passed by valueが異なる
- 例えば、sliceはPassed by reference, arrayはpassed by value
package main import "fmt" type person struct { name string } func changeSlice(s []int) { s[1] = 333 } func changeSlice2(s *[]int) { t := *s t[1] = 333 } func unchangeArray(a [3]int) { a[1] = 333 } func changeArray(a *[3]int) { a[1] = 333 } func main() { s := []int{1, 2, 3} changeSlice(s) fmt.Println(s) t := []int{1, 2, 3} changeSlice2(&t) fmt.Println(t) a := [3]int{1, 2, 3} unchangeArray(a) fmt.Println(a) changeArray(&a) fmt.Println(a) }
slice vs array
-
arrayはサイズが決めてる
-
sliceはサイズが決まってない
var slice []int var array [3]int fmt.Println(slice) // [] fmt.Println(array) // [0 0 0] fmt.Println(reflect.TypeOf(slice).Kind()) // slice fmt.Println(reflect.TypeOf(array).Kind()) // array
-
loop
var a [6]int var b []int = []int{0, 1, 2, 3, 4, 5} for k, v := range a { fmt.Printf("%d, %d\n", k, v) } for k, v := range b { fmt.Printf("%d, %d\n", k, v) }
関数、変数などをpackage外に公開するため、命名を大文字から始める必要がある
type User struct { // 公開
ID int64
}
type user struct { // 非公開
ID int64
}
struct 構造体
フィールドがあるデータ構造
type Person struct {
name string
age int
}
func main() {
p := Person{name: "tom", age: 20}
fmt.Print(p)
}
-
structにmethodを定義する
-
methodはreceiverがある関数で、他の言語でのinstance methodみたいなものです
-
receiverはPassed by value, Passed by referenceの二種類がある
package main import ( "fmt" ) type person struct { firstName string lastName string age int } // Passed by reference(パラメータがPointer type) // cがreceiverである func (c *person) name() string { c.lastName = "changed" return c.lastName + " " + c.firstName } // Passed by value // cがreceiverである func (c person) name2() string { c.lastName = "no changed" return c.lastName + " " + c.firstName } func main() { p := person{firstName: "tom", lastName: "jerry", age: 4} fmt.Println(p.name()) fmt.Println(p.lastName) // changed p2 := person{firstName: "tom", lastName: "jerry", age: 4} fmt.Println(p2.name2()) fmt.Println(p2.lastName) // jerry 元のまま }
-
-
constructorが存在しない
独自の初期化処理を入れたい場合、factory functionを自分で定義する必要があるpackage main import ( "fmt" ) type User struct { id int name string } func NewUser(id int, name string) *User { // factory function if name == "" { name = "Anonymous user" } return &User{id, name} } func main() { // 初期化する方法 u := User{} // ① fmt.Println(u) // {0 } u1 := new(User) // ② new関数を使うと、pointerが設定される fmt.Println(u1) // &{0 } u2 := NewUser(1, "tom") fmt.Println(u2) // &{1 tom} }
-
embeded struct
package main import ( "fmt" ) type name struct { first string last string } type User struct { id int name name } type User2 struct { id int name // embeded struct } func main() { u := User{name: struct{first: "first name", last: "last name"}} fmt.Println(u.name.first) // fmt.Println(u.first) // エラー: u.first undefined u2 := User2{name: name{first: "first name2", last: "last name2"}} // u2 := User2{first: "first name2", last: "last name2"} // 初期化するときはnameを指定する必要があります。ここではunknown field firstエラーが発生 fmt.Println(u2.name.first) fmt.Println(u2.first) // アクセスするときは、nameを経由しなくても良い、railsのdelegateみたいなもの }
-
embeded structは便利そうに見えるけど、注意点がある
-
embed先、元が同じ項目ある場合、分かりづらい
package main import "fmt" type pet struct { name string } type person struct { name string pet } func main() { p := person{name: "Deitch era", pet: pet{name: "tom"}} fmt.Println(p.name) // nameはpersonの? petの? }
-
embedされてstructの項目がpublicになって、必要以上の詳細がpublicなることが発生する
package model type Person struct { pet1 pet2 pet2 } type pet1 struct { Foods []string } type pet2 struct { Foods []string } func (p *Person) FeedPet2(food string) bool { if food == "唐辛子" { return false } p.pet2.Foods = append(p.pet2.Foods, food) return true } // 以下は別のpackage func main() { p := Person{} // pet1のFoodsは外部packageにpublicされてるので、直接pet1に何もあげることができる p.Foods = append(p.Foods, "唐辛子") fmt.Println(p.Foods) // [唐辛子] // p.pet2やp.pet2のFoodsも直接アクセスできないので、publicしたFeedPet2 methodは利用するしかない p.FeedPet2("唐辛子") fmt.Println(p.pet2.Foods) // [] }
-
-
anonymous struct
package main import ( "fmt" ) type User struct { id int name struct { // anonymous struct first string last string } } func rect(width int, height int) struct{ width, height int } { a := struct{ width, height int }{width, height} // anonymous struct return a } func main() { a := rect(2, 4) fmt.Println(a.width) u := User{id: 5, name: struct { first string last string }{first: "first name", last: "last name"}} fmt.Println(u) }
Interfaces
- Interfaces are implemented implicitly
- 外部packageのstructにいろんなmethodがあって、自分が依存してるmethodだけをinterfaceとして定義して、利用できます
package main
import (
"fmt"
"time"
)
type unixDate interface {
Unix() int64
}
func main() {
var date unixDate
date = time.Date(2022, 4, 1, 9, 0, 0, 0, time.Local)
// time.DateのUnix() methodとunixDate interfaceのUnix()のfunction signatureが同じなので、time.DateがunixDate interfaceを実現したことになる
fmt.Println(date.Unix())
//fmt.Println(date.Year()) // time.DateにはYear() methodがありますが、unixDate interfaceにはないので、利用できない
}
Function is First Class
functionを値として使える
package main
import "fmt"
type filterFunc[E any] func(E) bool // 関数型のtype定義
func Filter[E any](s []E, f filterFunc[E]) []E {
result := []E{}
for _, v := range s {
if f(v) {
result = append(result, v)
}
}
return result
}
func onlyEven(v int) bool {
return v%2 == 0
}
var filterEven = onlyEven // 関数を変数に設定できる
func main() {
s := []int{1, 2, 3, 4}
fmt.Println(Filter(s, onlyEven)) // 関数を関数の引数に渡す
fmt.Println(Filter(s, filterEven))
}
deferの役割
- 関数がreturnするまでに処理の実行を遅延する仕組みで、実行順番はLIFO
package main
import (
"fmt"
)
func trace(s string) string {
fmt.Println("entering:", s)
return s
}
func un(s string) {
fmt.Println("leaving:", s)
}
func a() {
defer un(trace("a"))
fmt.Println("in a")
}
func b() {
defer un(trace("b"))
fmt.Println("in b")
a()
defer fmt.Println("finish b")
}
func main() {
b()
fmt.Println("finish main")
}
// 出力は
// entering: b
// in b
// entering: a
// in a
// leaving: a
// finish b
// leaving: b
// finish main
- 利用シーン、例えば、resourceの回収に使う
func Contents(filename string) (string, error) {
f, err := os.Open(filename)
if err != nil {
return "", err
}
defer f.Close() // f.Close will run when we're finished.
.....
}
type assertion
typeをany(interface{})に宣言した場合、実際の値のtypeをして扱う前にType assertionを行う必要がある
var a any
a = "2,3"
fmt.Println(strings.Split(a, ",")) // errorが発生: ./prog.go:11:28: cannot use a (variable of type any) as string value in argument to strings.Split: need type assertion
fmt.Println(strings.Split(a.(string), ",")) // OK
pointer
- pointerは値の住所って理解すれば、分かりやすいです
-
*
は使うところにより、意味が異なる- typeとして使う場合、pointer typeを意味する。
var x *int
はintのpointer(住所)を意味する - 操作として使う場合、
*p
はpointerが指してる値をアクセスする
- typeとして使う場合、pointer typeを意味する。
package main
import "fmt"
func main() {
i, j := 42, 2701
p := &i // &でiに格納した42の住所を取得してpに設定する
fmt.Println(p)
fmt.Println(*p) // *で、pointerが指してる値をアクセスできる
*p = 21 // pointerが指してる値を入れ替える
fmt.Println(i) // iが21になる
}
package main
import "fmt"
func unchanged(i int) {
i = 2
}
func changed(i *int) { // iはintのpointer type
*i = 2
}
func main() {
i := 1
unchanged(i)
fmt.Println(i)
changed(&i) // pointer(住所)を渡す
fmt.Println(i)
}
package main
import "fmt"
type person struct {
name string
}
func changename1(p *person) {
p.name = "xxx" // struct中の項目を変更するときは*p.nameにする必要がない
}
// func changename2(p *person) {
// *p.name = "xxx" // compile error
// }
func main() {
p := person{name: "tom"}
changename1(&p)
fmt.Println(p)
}
tag
struct fieldに付加されるメタデータです。さまざまなツールやライブラリに追加情報や指示を提供します。
書き方: field型の後に、tagName:"value"をバッククォート(`)で囲みます。
例えば、tagでvalidationを実現することが可能です
package main
import (
"fmt"
"reflect"
)
type MyStruct struct {
Name string `validate:"notzero"`
Age int `validate:"notzero"`
}
func validateStruct(s interface{}) []error {
var errors []error
v := reflect.ValueOf(s)
t := v.Type()
for i := 0; i < v.NumField(); i++ {
field := t.Field(i)
tag := field.Tag.Get("validate")
if tag == "notzero" {
fieldValue := v.Field(i)
zero := reflect.Zero(fieldValue.Type()).Interface()
if fieldValue.Interface() == zero {
errors = append(errors, fmt.Errorf("%s cannot be zero", field.Name))
}
}
}
return errors
}
func main() {
err := validateStruct(MyStruct{"John", 30})
fmt.Printf("Error: %s\n", err) // Error: []
err = validateStruct(MyStruct{})
fmt.Printf("Error: %s\n", err) // Error: [Name cannot be zero Age cannot be zero]
err = validateStruct(MyStruct{"tom", 0})
fmt.Printf("Error: %s\n", err) // Error: [Age cannot be zero]
err = validateStruct(MyStruct{"", 2})
fmt.Printf("Error: %s\n", err) // Error: [Name cannot be zero]
}
zero valueは何? nilは何?
宣言して、初期化されてない変数は自動でzero valueを設定されます。
例えば、
- intのzero valueは0
- booleanのzero valueはfalse
- stringのzero valueは""
- pointer, channel, func, interface, map, or slice typeのzero valueは
nil
です-
nil
は0, false, ""みたいに、特別なzero valueで考えれば良いと思います。
-
package main
import "fmt"
func main() {
var isTrue bool
var age int
var ch chan int
var numbers []int
fmt.Println(isTrue) // false
fmt.Println(age) // 0
fmt.Println(ch == nil) // true
fmt.Println(numbers == nil) // true
numbers = []int{1, 2, 3}
fmt.Println(numbers == nil) // false
numbers = nil
fmt.Println(numbers == nil) // true
}
主に参考した資料
https://go.dev/doc/effective_go
https://www.youtube.com/watch?app=desktop&v=N0fIANJkwic
https://www.oreilly.com/library/view/100-go-mistakes/9781617299599/
https://bard.google.com/chat