- 投稿日:2020-05-26T23:37:25+09:00
関数に可変長引数を使いたい
はじめに
可変長引数を使った関数について、備忘録を兼ねて記載しておく
概要
Goでは、「...」を使うことで
・関数の定義に可変長引数を使うこと
・その関数にスライスを渡すこと が可能可変長引数を使って任意個数の引数をとる関数を定義する
型の前に「...」を記載した(
変数名 ...型
)を引数の定義を使うことで、
任意の個数の引数をとる関数を定義することができる。sample.gofunc sample(s string, i ...int){ //処理(もちろん、iはスライスなので、rangeを使うことも可能) } func main(){ sample("Hello", 1, 2, 3, 4) }つまり、関数定義で使う「...」には、渡ってきた可変長引数の値を 変数名x([]T型)のスライスにまとめる効果がある。
制約事項
可変長引数は、「引数の末尾に1つだけ」定義することができる。
→ 可変長引数は、最後に1つだけしか書けないスライスを可変長引数として渡す
変数名の後に「...」を記載した(
変数名...
)を使って、スライスを可変長引数にして渡すことが可能sample.gofunc sample(s string, i ...int){ //処理(もちろん、iはスライスなので、rangeを使うことも可能) } func main(){ sl := []int{1, 2, 3, 4} sample("Hello", sl...) }つまり、引数のスライスに「...」を使うと、スライスを可変長引数に展開する効果がある
まとめ
型の前に「...」を記載した(
変数名 ...型
) には、可変長引数をスライスにまとめる
変数名の後に「...」を記載した(変数名...
) には、スライスを可変長引数に展開する
効果がある参照
- 翔泳社 スターティングGo言語 https://www.shoeisha.co.jp/book/detail/9784798142418
- 投稿日:2020-05-26T23:31:48+09:00
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=3Query を投げるためにテーブルを一つ作成しておきます。
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.gopackage 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.02020/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 connectionv1.4.0 の場合は
unexpected EOF
が発生し、mysql.ErrInvalidConn
が返却されます。v1.5.02020/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 OKv1.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 }(*DB).Ping
次に、(*DB).Ping でも確認してみましょう。
先程のコードのdb.Query
をdb.Ping
に変更します。main.gofor { if err := db.Ping(); err != nil { log.Fatal(err) } log.Print("OK") time.Sleep(1 * time.Second) }v1.4.02020/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 connectionv1.4.0 ではやはり
unexpected EOF
発生からのmysql.ErrInvalidConn
が返却されています。v1.5.02020/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) }一見すると、こちらも
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 の値を調整したほうがよさそうです。
- 投稿日:2020-05-26T22:39:07+09:00
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 -f
やkubectl apply -f
等で指定するあのファイルです。
- Helm Chart、Manifestファイル: deprecated/removed APIを使用したままの場合、Kubernetesクラスタ上のリソースを追加したり更新する時に失敗する可能性がある。
- Kubernetesクラスタ上のリソース: KubernetesによってInternal/Storage Versionに変換された場合、期待しないデフォルト値が設定されて想定外の挙動を引き起こす可能性がある。
- 例えばDaemonSetではAPIバージョンが
extensions/v1beta1
の時はspec.updateStrategy.type
のデフォルト値はOnDeleteだったが、apps/v1
ではデフォルト値がRollingUpdateに変更されている。- 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を開発運用することを想定する場合、このデメリットは大きな技術負債となる可能性が高いです。
- 投稿日:2020-05-26T17:28:02+09:00
【Go】メソッド=関数でしょ?と考えてきちんと躓く
メソッド = 関数でしょ?
なんででしょう。なんとなくこういう感覚でした。
もちろん違いますね。オブジェクト指向をきちんと勉強しましょうね(自戒)とりあえず、一歩前進して以下の解釈になりました。
関数とは
IT用語辞典 -- 関数 【 function 】 ファンクション
関数とは、コンピュータプログラム上で定義されるサブルーチンの一種で、数学の関数のように与えられた値(引数)を元に何らかの計算や処理を行い、結果を呼び出し元に返すもののこと。
うん。こちらの意味は想像と一致してました。
メソッドとは
IT用語辞典 -- メソッド 【 method 】 メンバ関数
オブジェクト指向ではデータと手続きをオブジェクトとして一体化(カプセル化)して定義、利用する。この、オブジェクトに内包された手続き(データに対する処理内容を記述したプログラム)のことをメソッドという。言語によっては「メンバ関数」などということもあるが、ほぼ同じ機能を表す。
オブジェクトに内包された手続き(データに対する処理内容を記述したプログラム)のことをメソッドという。(ここ大事)
Goで見てみる
関数
functions.gopackage main import "fmt" func add(x int, y int) int { return x + y } func main() { fmt.Println(add(42, 13)) }これはとっても理解できました。最初は
メソッド
Go does not have classes. However, you can define methods on types.
クラスはありませんが、types(型)に紐づく関数(メソッド)を定義できるとのこと。
methods.gopackage 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)からメソッドを呼び出すことができていますね!関数と違い、型と紐づいて初めてメソッドと呼ばれるようになるということだったのか。
前述のメソッドの意味ときちんと一致していますね。オブジェクトに内包された手続き(データに対する処理内容を記述したプログラム)のことをメソッドという。(ここ大事)
後書き
ようやく関数とメソッドの違いについて疑問に思うことができました。
正直なんとなく使っていた事に気づいて反省。
- 投稿日:2020-05-26T15:52:07+09:00
【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) }
- 投稿日:2020-05-26T03:03:59+09:00
【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 }