はじめに
Go言語で構造体にメソッドを生やす際の挙動の違いについて気になったので備忘録です。
JSの言語仕様と比べながら整理しました。
前提条件
- JavaScriptで参照渡し、値渡しを知っていると理解を助けると思います
JSとGoのクラスとメソッド
JS
class User {
name: string;
age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
birthday() {
this.age++;
}
}
// teeというobjectを作成する
// メソッドは__proto__を辿ってUserのメソッドをインスタンス間で共有する
const tee = new User("tee", 20);
// メソッドは共有するが、thisは呼び出し元(今回はteeインスタンス)を指すので、同じ参照先のobjectを変更する
tee.birthday();
console.log(tee.age); // 21
Go
メソッドでレシーバを指定する際に、値を渡すが、アドレスを渡すか明示的に選ぶのがポイント
// レシーバに「呼び出し元の値」と「呼び出し元のアドレス値」のどちらをコピーするか決める
// ポインタレシーバもアドレスを明示的に渡す「値渡し」として扱われ、参照渡しとは呼ばない
// 構造体でメソッド全体を値レシーバにするか、ポインタレシーバにするかは共通化しておくのが良い
package main
import "fmt"
type User struct {
Name string
Age int
}
// (u User)はメソッド呼び出し時に、uに値をコピーするのか、アドレスをコピーするのか決めている
// 値レシーバ(u User) uに値をコピーする
// ポインタレシーバ(u *User) uにアドレスをコピーする
func (u *User) birthday() {
// uには呼び出し元のアドレスがコピーされているので、呼び出し元に影響する
u.Age++
}
func main() {
var tee User
tee.Name = "tee"
tee.Age = 20
tee.birthday()
fmt.Println(tee.Age) // 21 呼び出し元に影響する
}
JSの参照渡しとGoのポインタ型
JavaScriptのオブジェクト参照(参照渡し) も Goのポインタ型も低レイヤー状での実装は非常に良く似ている
JavaScript
オブジェクトへの参照値をコピーする。
Go
対象の値を指すポインタ値(実際のメモリアドレス)をコピーする。
JavaScriptの言語仕様では、オブジェクトへの参照がメモリ上でどのように表現されるかは規定されていない。具体的な実装はJavaScriptエンジンに委ねられている。
V8エンジンを例に出すと、オブジェクトへの参照にメモリアドレスに基づくポインタ値を使用している。タグ付きポインタや圧縮ポインタなどの仕組みも利用される。
※ 厳密なアドレスではないがそこに紐づくもの
低レイヤーでは仕組みはほぼ同じ。アドレスを起点にしている。
JavaScriptはオブジェクトは必ず参照、プリミティブ型は値。 ※ 変数に代入したときの性質
Go言語は明示的に指定できる。
JavaScriptも変数への代入は「参照値」か「値」のコピーなのでGo同様に値渡しだが、参照値を渡すときは通称として「参照渡し」と呼ばれる文化がある。 Goにこの文化はない
おわりに
JavaScriptでオブジェクトを他の変数に代入する際は、参照をコピーする「参照渡し」です。参照値は漠然とアドレス情報を裏でごにょごにょしてるんだろうな。くらいの認識でした。
今回JSエンジンに委ねられていて、具体的な仕様が定められていない点を知れてよかったです。
この辺の呼称は文化による違いがあるので慣れるまでむずかいですね