3
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

GLOBISAdvent Calendar 2023

Day 24

Go言語学習メモ

Posted at

前書き

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が指してる値をアクセスする
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

3
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?