20200526のGoに関する記事は6件です。

関数に可変長引数を使いたい

はじめに

可変長引数を使った関数について、備忘録を兼ねて記載しておく

概要

Goでは、「...」を使うことで
・関数の定義に可変長引数を使うこと
・その関数にスライスを渡すこと が可能

可変長引数を使って任意個数の引数をとる関数を定義する

型の前に「...」を記載した(変数名 ...型)を引数の定義を使うことで、
任意の個数の引数をとる関数を定義することができる。

sample.go
func sample(s string, i ...int){
      //処理(もちろん、iはスライスなので、rangeを使うことも可能)
}

func main(){
    sample("Hello", 1, 2, 3, 4)
}

つまり、関数定義で使う「...」には、渡ってきた可変長引数の値を 変数名x([]T型)のスライスにまとめる効果がある。

制約事項

可変長引数は、「引数の末尾に1つだけ」定義することができる。
→ 可変長引数は、最後に1つだけしか書けない

スライスを可変長引数として渡す

変数名の後に「...」を記載した(変数名...)を使って、スライスを可変長引数にして渡すことが可能

sample.go
func sample(s string, i ...int){
      //処理(もちろん、iはスライスなので、rangeを使うことも可能)
}

func main(){
    sl := []int{1, 2, 3, 4}
    sample("Hello", sl...)
}

つまり、引数のスライスに「...」を使うと、スライスを可変長引数に展開する効果がある

まとめ

型のに「...」を記載した(変数名 ...型) には、可変長引数をスライスにまとめる
変数名のに「...」を記載した(変数名...) には、スライスを可変長引数に展開する
効果がある

参照

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

go-sql-driver/mysql を用いる際に (*DB).SetConnMaxLifetime は不要なのか

はじめに

先日、GitHub Blog にて Go の MySQL Driver に関する記事が掲載されました。
The GitHub Blog: Three bugs in the Go MySQL Driver

ここでは Three bugs のうち、最初に挙げられている The crash について話します。

The crash は、切断されたコネクションをコネクションプールから引き当てて用いた際に unexpected EOF が発生する問題です。
この問題に対して、今までは (*DB).SetConnMaxLifetime を設定することで、切断されたコネクションを再利用しないよう対処してきました。

go-sql-driver/mysql v1.5.0 では著者による修正がマージされており、記事中では (*DB).SetConnMaxLifetime が不要とされています。

If your MySQL server uses idle timeouts, or is actively pruning connections, you don’t need to call (*DB).SetConnMaxLifetime in your production services. It’s no longer needed as the driver can now gracefully detect and retry stale connections. Setting a max lifetime for connections simply causes unnecessary churn by killing and re-opening healthy connections.

しかし、時折コネクション周りのエラーログを見かけることがあり、挙動が気になったため簡単に検証してみました。

結論

記事にある通り、TCP コネクションが切断されていてもリトライ処理が行われるため、(*DB).SetConnMaxLifetime は必須ではなくなりました。
ただし、(*DB).Ping についてはリトライすることなく driver.ErrBadConn が返却されるため注意が必要です。

挙動を確認してみる

準備

まず、MySQL を --wait-timeout=3 として起動しておきます。

docker run --rm -d --name the-crash-mysql -p 3306:3306 \
  -e MYSQL_ROOT_PASSWORD=password -e MYSQL_USER=user -e MYSQL_PASSWORD=password \
  -e MYSQL_DATABASE=mydb \
  mysql/mysql-server:8.0 --wait-timeout=3

Query を投げるためにテーブルを一つ作成しておきます。

mysql -h127.0.0.1 -P3306 -uuser -ppassword mydb -e "CREATE TABLE users (id int, name varchar(10))"

(*DB).Query

最初に wait_timeout に設定した値より少し大きい数のコネクションを張っておき、以後 1 秒間隔で Query を投げます。

main.go
package main

import (
    "database/sql"
    "log"
    "sync"
    "time"

    _ "github.com/go-sql-driver/mysql"
)

func main() {
    db, err := sql.Open("mysql", "user:password@tcp(localhost:3306)/mydb")
    if err != nil {
        log.Fatal(err)
    }
    defer db.Close()

    const numOfConns = 10
    db.SetMaxIdleConns(numOfConns)
    db.SetMaxOpenConns(numOfConns)
    // db.SetConnMaxLifetime は設定しない

    var wg sync.WaitGroup
    for i := 0; i < numOfConns; i++ {
        wg.Add(1)
        go func() {
            rows, err := db.Query("SELECT * from users")
            if err != nil {
                log.Fatal(err)
            }
            if err := rows.Close(); err != nil {
                log.Fatal(err)
            }
            wg.Done()
        }()
    }
    wg.Wait()
    log.Printf("idle conns: %d", db.Stats().Idle)

    for {
        rows, err := db.Query("SELECT * from users")
        if err != nil {
            log.Fatal(err)
        }
        if err := rows.Close(); err != nil {
            log.Fatal(err)
        }
        log.Print("OK")
        time.Sleep(1 * time.Second)
    }
}

go-sql-driver/mysql のバージョンを変えて実行してみます。

v1.4.0
2020/05/26 01:50:16 idle conns: 10
2020/05/26 01:50:16 OK
2020/05/26 01:50:17 OK
2020/05/26 01:50:18 OK
[mysql] 2020/05/26 01:50:19 packets.go:36: unexpected EOF
2020/05/26 01:50:19 invalid connection

v1.4.0 の場合は unexpected EOF が発生し、mysql.ErrInvalidConn が返却されます。

v1.5.0
2020/05/26 01:50:34 idle conns: 10
2020/05/26 01:50:34 OK
2020/05/26 01:50:35 OK
2020/05/26 01:50:36 OK
[mysql] 2020/05/26 01:50:37 packets.go:122: closing bad idle connection: EOF
[mysql] 2020/05/26 01:50:37 packets.go:122: closing bad idle connection: EOF
2020/05/26 01:50:37 OK
[mysql] 2020/05/26 01:50:38 packets.go:122: closing bad idle connection: EOF
[mysql] 2020/05/26 01:50:38 packets.go:122: closing bad idle connection: EOF
2020/05/26 01:50:38 OK
[mysql] 2020/05/26 01:50:39 packets.go:122: closing bad idle connection: EOF
[mysql] 2020/05/26 01:50:39 packets.go:122: closing bad idle connection: EOF
2020/05/26 01:50:39 OK

v1.5.0 の場合は closing bad idle connection: EOF が2回発生した後、OKのログが出力されています。

go-sql-driver/mysql#934 の修正により、パケットを送信する前に TCP レベルで接続確認を行い、切断時には driver.ErrBadConn が返却されるようになりました。
(*DB).QueryContext では driver.ErrBadConn 発生時に、 maxBadConnRetries (2回) リトライし、それでもダメなら新規コネクションを用いるような実装になっているため、(*DB).SetConnMaxLifetime が設定されていなくても問題なくなったわけです。

(*DB).QueryContext
// QueryContext executes a query that returns rows, typically a SELECT.
// The args are for any placeholder parameters in the query.
func (db *DB) QueryContext(ctx context.Context, query string, args ...interface{}) (*Rows, error) {
    var rows *Rows
    var err error
    for i := 0; i < maxBadConnRetries; i++ {
        rows, err = db.query(ctx, query, args, cachedOrNewConn)
        if err != driver.ErrBadConn {
            break
        }
    }
    if err == driver.ErrBadConn {
        return db.query(ctx, query, args, alwaysNewConn)
    }
    return rows, err
}

ref.) https://github.com/golang/go/blob/f65ad0dda7ffef9397d1aaa47259ad4d4f12474f/src/database/sql/sql.go#L1592-L1607

(*DB).Ping

次に、(*DB).Ping でも確認してみましょう。
先程のコードの db.Querydb.Ping に変更します。

main.go
    for {
        if err := db.Ping(); err != nil {
            log.Fatal(err)
        }
        log.Print("OK")
        time.Sleep(1 * time.Second)
    }
v1.4.0
2020/05/26 01:51:02 idle conns: 10
2020/05/26 01:51:02 OK
2020/05/26 01:51:03 OK
2020/05/26 01:51:04 OK
[mysql] 2020/05/26 01:51:05 packets.go:36: unexpected EOF
2020/05/26 01:51:05 invalid connection

v1.4.0 ではやはり unexpected EOF 発生からの mysql.ErrInvalidConn が返却されています。

v1.5.0
2020/05/26 01:51:18 idle conns: 10
2020/05/26 01:51:18 OK
2020/05/26 01:51:19 OK
2020/05/26 01:51:20 OK
[mysql] 2020/05/26 01:51:21 packets.go:122: closing bad idle connection: EOF
2020/05/26 01:51:21 driver: bad connection

一方、v1.5.0 では修正されているんだよね!と思いきや、driver.ErrBadConn が返ってきます。
(*DB).PingContext の実装を見てみると...

(*DB).PingContext
// PingContext verifies a connection to the database is still alive,
// establishing a connection if necessary.
func (db *DB) PingContext(ctx context.Context) error {
    var dc *driverConn
    var err error

    for i := 0; i < maxBadConnRetries; i++ {
        dc, err = db.conn(ctx, cachedOrNewConn)
        if err != driver.ErrBadConn {
            break
        }
    }
    if err == driver.ErrBadConn {
        dc, err = db.conn(ctx, alwaysNewConn)
    }
    if err != nil {
        return err
    }

    return db.pingDC(ctx, dc, dc.releaseConn)
}

ref.) https://github.com/golang/go/blob/f65ad0dda7ffef9397d1aaa47259ad4d4f12474f/src/database/sql/sql.go#L792-L812

一見すると、こちらも driver.ErrBadConn 発生時にリトライしているように見えますが、(*DB).conn はコネクションプールからコネクションを取り出すだけ。
この時点ではコネクションが切断されたかを知ることは出来ません。
(*DB.pingDC) が呼ばれて初めて、 (*mysqlConn).writePacket にてコネクションの確認が行われるため、リトライされることなく driver.ErrBadConn が返却されてしまうのです。

Query 実行時はリトライして処理を継続できるケースなのに、Ping の場合にエラー扱いになるのは少し違和感がありますね。

おわりに

(*DB).SetConnMaxLifetime を設定しなくても、多くの場合、リトライして処理が継続されるため必須ではなくなりました。
ただし、(*DB).Ping についてはリトライすることなく driver.ErrBadConn が返却されます。
Pod の Readiness Probe Endpoint などに (*DB).Ping を活用している場合は、即時失敗とみなさずリトライ処理を行うか、failureThreshold の値を調整したほうがよさそうです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Kubernetes APIのdeprecation/removalを事前に検出する

apps/v1beta1 Deployment等のKubernete APIのdeprecation/removalを検出するための方法について紹介します。

特にKubernetes v1.16以上に移行する方々
Kubernetes v1.16ではDaemonSetやDeploymentの古いAPIバージョン(ex. extensions/v1beta1, apps/v1beta2)がremoval対象となっているため影響が大きいです。v1.16以上のクラスタに移行する予定がある方は注意しましょう。

背景

Kubernetesは新しいバージョンで特定のKubernetes API(ex. apps/v1beta1 Deployment)がdeprecated/removedとなります。詳細なDeprecation Policyについては公式ドキュメントを参照してください。

Kubernetesでは各リソースのAPIバージョンをInternal Version -> Storage Versionに変換する仕組みがあります(Internal/Storage Versionに興味がある人はこちらのスライド等を参照)。このためKubernetesクラスタ上に登録されているリソースはdeprecated/removedされていないAPIバージョンに変換済みで、Kubernetesクラスタをアップグレードしても直ちに問題は発生しないかもしれません。

しかしアップグレード後の運用を考慮した場合、以下の3つの課題に遅かれ早かれ当たることになります。

NOTE: 以降で登場するManifestファイルとは、Pod, Deployment, ConfigMap等を記述したYAMLファイルを指します。kubectl create -fkubectl apply -f等で指定するあのファイルです。

  1. Helm Chart、Manifestファイル: deprecated/removed APIを使用したままの場合、Kubernetesクラスタ上のリソースを追加したり更新する時に失敗する可能性がある。
  2. Kubernetesクラスタ上のリソース: KubernetesによってInternal/Storage Versionに変換された場合、期待しないデフォルト値が設定されて想定外の挙動を引き起こす可能性がある。
    • 例えばDaemonSetではAPIバージョンがextensions/v1beta1の時はspec.updateStrategy.typeのデフォルト値はOnDeleteだったが、apps/v1ではデフォルト値がRollingUpdateに変更されている。
  3. Controller/Operator: Kubernetesクラスタで利用しているカスタムのController/Operatorはそのプログラムの中で特定バージョンのKubernetes APIのリソースを作成したりアクセスしている場合がある。もしも該当するKubernetes APIがremovedとなった場合、該当するKubernetes APIリソースを操作する時に失敗するなどの問題が発生する可能性がある。
    • Controller/Operator以外にもKubernetes APIリソースを作成したり登録するようなCLIツール等についても基本的に同じことが言えます。

これらの課題の困ったところは「アップグレード直後は問題は発覚せず、運用中のある操作を行ったりした時だったり、何度もアップグレードを重ねた時に初めて発覚する可能性がある」という点です。

このためKubernetesクラスタのアップグレード前にKubernetes APIのdeprecation/removalを検出して適切な変更を行う必要があります。しかし現在のKubernetesはそのような検出するための仕組みやツールは公式に提供されていません。しかし以降で紹介する各種ツール等を活用することでAPI deprecation/removalを検出できます。

検出方法

各パターンのAPI deprecation/removalを検出するためのツールについて紹介します。
インストールや使用方法については、各ツールの公式レポジトリや関連URLを参照してください。

Helm Chart/Manifestファイル

結論: Plutoを使用する。

Helm Chart、Manifestファイル(YAML)のAPI deprecadtion/removalを検出するためのツールはいくつか存在します。基本的にPlutoがお勧めですが、汎用性や柔軟性の高さを重視する場合はconftestの方が良いかもしれません。

  • conftest
    • Open Policy Agentベースのチェックツール。幅広い用途で利用できる。
    • swade1987/deprek8ionで各Kubernetesバージョンのdeprecation/removal検出ポリシーが提供されてる。(関連記事)
    • Helm Chartのチェックには対応していない。
      • helm templateを併用すればチェック可能。
  • Pluto
    • Kubernetes API deprecation/removal検出に特化したツール。
    • Helm Chart(v2/v3)とManifestファイルの両方に対応。
      • Helm Chartのチェックはhelm templateを利用するため、helm CLIのインストールが必須。
    • HelmについてはKubernetesクラスタ上のリソースチェックも可能。
    • CLIバイナリのみで利用可能。
    • API deprecation/removal情報はハードコーディングされてるため柔軟性は低い。

Kubernetesクラスタ上のリソース (非推奨)

結論: kubentを使用する。ただし非推奨。

Helm ChartやManifestファイルをCI/CDの中で自動生成してそのまま適用するような場合、Kubernetesクラスタ上にしかリソースがないため、こちらをチェックする必要があります。

kubentを使用することで、Kubernetesクラスタ上のリソースのAPI deprecation/removalを検出できます。Helm(v2/v3)およびkubectl applyで登録したリソースに対してチェックをかけることができます。

ただしここで注意して欲しいのはkubectl applyではなくkubectl createで登録したリソースに対してはチェックができないことです。このためこれらのリソースについてはAPI deprecation/removalの検出はできません
これはkubentの制約というよりも、補足に記載しているKubernetes自体に起因するものです。このためクラスタ上のリソースに対しては極力チェックしなくても済むようにGitOps等をベースにしたCI/CD環境を整えて、Helm ChartやManifestファイルのチェックを行えるようにすることをお勧めします。

補足: 何故kubectl applyだと検出できるのか?

Kubernetesではリソースを登録すると、そのリソースはデフォルトのAPIバージョンに変換されます。例えばextensions/v1beta1:DaemonSetのリソースを登録しても、DaemonSetのデフォルトAPIバージョンがapps/v1であればapps/v1:DaemonSetに変換されてリソースが登録されます。つまりこの時点でKubernetesサーバ側のetcd上ではもともとのextensions/v1beta1というAPIバージョン情報は存在しないことになります。これはkubectl createでもkubectl applyでも同様です。

しかしkubectl applyの場合は、リソースを登録する時に登録時点のManifest情報がそのリソースのkubectl.kubernetes.io/last-applied-configurationアノテーションに設定されます。このManifest情報には登録時のapiVersion等も含まれるため、前述したデフォルトAPIバージョンへのリソース変換が実施されてもアノテーションとしてAPIバージョン情報が残ります。

kubentはkubectl.kubernetes.io/last-applied-configurationアノテーションを参照しているため、適切にAPI deprecation/removalを検出できるわけです。

Controller/Operator

結論: Go言語 + kubernetes/client-goの実装であればyoichiwo7/k8sdeprを使用する。(ただしツールはwip=WorkInProgress)

NOTE:
既により良いものがあるとか、k8sdeprの問題点や改善に関する指摘があればお願いします。

個人的にKubernetes API deprecation/removal検出が最も難しいのがController/Operatorだと考えています。

自身で開発しているController/Operatorであれば各Kubernetesバージョンのクラスタをkind(kubernetes in docker)で用意してテストを動かすことで検出したりできます。しかしそこまでテスト構成を整えるのはそれなりの規模でないと厳しいのが実情だと思います。
またKubernetes上にインストールしてあるサードパーティのController/Operatorである場合、その全てのプロジェクトに対してそのようなテストを追加してもらうことは現実的ではありません。

このためコード静的解析によるAPI deprecation/removalの検出が最も現実的な落としどころの一つとなります。このようなツールであれば開発やCIに組み込むことも可能ですし、サードパーティのController/Operatorに対してチェックをかけることも比較的容易です。自分で探した範囲ではそのようなツールは存在しなかったため、Go言語のKubernetes APIのdeprecation/removalを検出するためのツール(Go Analyzer)を自作して使用しています。(yoichiwo7/k8sdepr@wip)

Go言語プロジェクトに対してツールを実行することにより、Kubernetes APIのdeprecation/removalがあるかを検出してくれます。例として、サードパーティのOperatorであるspotahome/redis-operator v0.5.0に対してAPI deprecation/removalの検出チェックを実行した結果の抜粋を以下に示します。

# Kubernetes v1.16.0を対象バージョンとして指定
$ k8sdepr -targetVersion v1.16.0 ./...
/tmp/redis-operator/service/k8s/deployment.go:18:42: apps/v1beta2:Deployment is removed. Migrate to apps/v1:Deployment. {deprecated=v1.9.0, removed=v1.16.0} 
...
/tmp/redis-operator/service/k8s/statefulset.go:18:43: apps/v1beta2:StatefulSet is removed. Migrate to apps/v1:StatefulSet. {deprecated=v1.9.0, removed=v1.16.0}

上記のようにどのKubernetesバージョンでdeprecated/removedされたかの情報、新しく使用すべきKubernetes APIも併せて出力するようになっています。

NOTE:
ちなみにspotahome/redis-operator v0.5.0等のようにGo Module未対応のプロジェクトの場合は、go mod init等を実行した上で一回Go Moduleプロジェクトに変換した状態でツールを実行する必要があります。Go Moduleの普及は進んでいますが、まだまだ未対応のプロジェクトは多いので留意しておく必要があります。

なおGo言語 + kubernetes/client-go以外で実装されてるような本流から外れたController/Operatorのチェックには対応していません。Go言語以外で実装されている場合は地道にgrepしたりして目視確認するか、コード静的解析によるツールをその言語向けに実装する必要があります。

補足: Go言語とKubernetesエコシステムに寄り添う大切さ

ちなみにyoichiwo7/k8sdeprは以下の前提/割り切りをもとに作成してます。

  • 主要なController/OperatorはGo言語 + kubernetes/client-goで実装されている
  • Go言語は静的解析のためのモジュール群が豊富であるため実装が比較的容易
  • Kubernetes APIのパッケージ構成が体系化されている
    • 基本的にk8s.io/api/配下に各種apiVersionおよびkindが配置されている

このような前提があるため、Go言語およびKubernetesエコシステムから大きく離れた形で実装されたController/Operatorに対しては適切な検出ができない可能性が高いです。

Controller/OperatorをJavaやPython等で実装するためのプロジェクトがいくつか存在しますが、それを選択した時点でGo言語およびKubernetesエコシステムの恩恵を受けられなくなる、または恩恵を受けるために独自の工夫が必要となる可能性が高くなります。慣れ親しんだ言語を使えるメリットと、恩恵を受けられなくなるデメリットのトレードオフは慎重に検討しましょう。
特に中長期にController/Operatorを開発運用することを想定する場合、このデメリットは大きな技術負債となる可能性が高いです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Go】メソッド=関数でしょ?と考えてきちんと躓く

メソッド = 関数でしょ?

なんででしょう。なんとなくこういう感覚でした。
もちろん違いますね。オブジェクト指向をきちんと勉強しましょうね(自戒)

とりあえず、一歩前進して以下の解釈になりました。

関数とは

IT用語辞典 -- 関数 【 function 】 ファンクション

関数とは、コンピュータプログラム上で定義されるサブルーチンの一種で、数学の関数のように与えられた値(引数)を元に何らかの計算や処理を行い、結果を呼び出し元に返すもののこと。

うん。こちらの意味は想像と一致してました。

メソッドとは

IT用語辞典 -- メソッド 【 method 】 メンバ関数

オブジェクト指向ではデータと手続きをオブジェクトとして一体化(カプセル化)して定義、利用する。この、オブジェクトに内包された手続き(データに対する処理内容を記述したプログラム)のことをメソッドという。言語によっては「メンバ関数」などということもあるが、ほぼ同じ機能を表す。

オブジェクトに内包された手続き(データに対する処理内容を記述したプログラム)のことをメソッドという。(ここ大事)

Goで見てみる

関数

A Tour of Go -- Functions

functions.go
package main

import "fmt"

func add(x int, y int) int {
    return x + y
}

func main() {
    fmt.Println(add(42, 13))
}

これはとっても理解できました。最初は

メソッド

A Tour of Go -- Methods

Go does not have classes. However, you can define methods on types.

クラスはありませんが、types(型)に紐づく関数(メソッド)を定義できるとのこと。

methods.go
package main

import (
    "fmt"
    "math"
)

type Vertex struct {
    X, Y float64
}

func (v Vertex) Abs() float64 {
    return math.Sqrt(v.X*v.X + v.Y*v.Y)
}

func main() {
    v := Vertex{3, 4}
    fmt.Println(v.Abs())
}

初見でコレを見たときは、関数定義時のこの点がよく理解できなかった

func (v Vertex) Abs() float64 {
     ↑↑これは?↑↑

こちらもきちんと説明がありました。

A method is a function with a special receiver argument.

The receiver appears in its own argument list between the func keyword and the method name.

これはreceiver(レシーバー)と言い、どのtypes(型)と紐づいているかを明示しているとのこと。
そのため、main内では、Vertex(types)からメソッドを呼び出すことができていますね!

関数と違い、型と紐づいて初めてメソッドと呼ばれるようになるということだったのか。
前述のメソッドの意味ときちんと一致していますね。

オブジェクトに内包された手続き(データに対する処理内容を記述したプログラム)のことをメソッドという。(ここ大事)

後書き

ようやく関数とメソッドの違いについて疑問に思うことができました。
正直なんとなく使っていた事に気づいて反省。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Golang】構造体③埋め込み

【Golang】構造体③埋め込み

Golangの基礎学習〜Webアプリケーション作成までの学習を終えたので、復習を兼ねてまとめていく。 基礎〜応用まで。

//Embedded
//クラスの継承のイメージ
//埋め込み



//Pythonの場合
/*
class Vertex(object):

    def __init__(self, x, y):
        self._x = x
        self._y = y

    def area(self):
        return self._x * self._y

    def scale(self, i):
        self._x = self._x * i
        self._y = self._y * i


class Vertex3D(Vertex):

    def __init__(self, x, y, z):
        super().__init__(x, y)
        self._z = z

    def area_3d(self):
        return self._x * self._y * self._z

    def scale_3d(self, i):
        self._x = self._x * i
        self._y = self._y * i
        self._z = self._z * i


v = Vertex3D(3, 4, 5)
v.scale(10)
print(v.area())
print(v.area_3d())
*/


package main


import (
    "fmt"
)

type Vertex struct {
    x, y int
}

func (v *Vertex) Scale(i int) {
    v.x = v.x * i
    v.y = v.y * i
}

//3D
type Vertex3D struct {
    //埋め込み
    //上記で作成した、Vertexを渡すと中身をそのまま渡せる
    Vertex
    z int
}


func NewV3D(x, y, z int) *Vertex3D {
    //埋め込みなので、このように書く。
    //return &Vertex3D{3, 4, 5}ではできない
    return &Vertex3D{Vertex{x, y}, z}
}

func main() {
    v3d := NewV3D(3, 4, 5)

    //どちらでもアクセスできる
    fmt.Println(v3d.x)
    fmt.Println(v3d.Vertex.x)


    fmt.Println(v3d)

    v3d.Scale(5)

    fmt.Println(v3d)
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Golang】構造体②メソッド

【Golang】構造体②メソッド

Golangの基礎学習〜Webアプリケーション作成までの学習を終えたので、復習を兼ねてまとめていく。 基礎〜応用まで。

package main 
//メソッドとポインタレシーバーと値レシーバー
//メソッド class内関数のselfを使えるようなイメージ(Goにはクラスが無い)
//コードがわかりやすい


import (
    "fmt"
)


//バーテックスを作成
type Vertex struct {
    X, Y int
}


//1
func Scale1(v *Vertex, i int) {
    v.X = v.X * i
    v.Y = v.Y * i   
}

//2
//メソッド
func (v Vertex) Area() int {
    return v.X * v.Y
}

//メソッド
//structのポインタを渡す 値を上書きする
//原則ポインタ型にする
//引数も渡す
func (v *Vertex) Scale2(i int) {
    v.X = v.X * i
    v.Y = v.Y * i
}

func main() {
    v := Vertex{3, 4}
    fmt.Println(v)

    //1
    //関数の場合
    Scale1(&v, 2)
    fmt.Println(v)
    //>>{6 8}

    //2 
    //メソッドの場合 pythonのselfのイメージ
    v.Scale2(2)
    fmt.Println(v)
    //>>{12 16}

    //scaleで書き換わった後なので
    fmt.Println(v.Area())
    //>192
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む