🐀

Go の大文字小文字による公開範囲とオブジェクト指向言語のアクセス修飾子の違い

2022/05/27に公開

概要

初めて Go を使い始めたとき、Go とオブジェクト指向言語のアクセス修飾子との対応について混乱しました。
少し調査をして整理をしたので、いまさらですがまとめます。

前提

Go の公開(exported)について

Go は大文字小文字で公開(exported)かどうかきまります。
勘違いしていたのですが、オブジェクト指向の public・private(・protected)という呼び方ではなく、exported かどうかと呼ばれるみたいです。
些細な言葉の違いですが、プログラミング言語 Go の著者も public・private と呼んでいない(以下を参照)ことから、Go の文脈に従い本記事では exported と呼ぶようにします。

Packages also let us hide information by controlling which names are visible outside the package, or exported. In Go, a simple rule governs which identifiers are exported and which are not: exported identifiers start with an upper-case letter.

出典:The Go Programming Language 2.6 Packages and Files

パッケージでは、どの名前がパッケージ外から見えるか、すなわち公開(exported)されるかを制御することで情報の隠蔽もできます。Go ではどの識別子が公開されるのかは、簡単な規則で決まっています。公開される識別子は大文字で始まるというものです。

出典:プログラミング言語 Go 2.6 パッケージとファイル

Go で非公開のことをなんて呼ぶか(unexported、 not exported とか)は「プログラミング言語 Go」では判断できませんでした。本記事では簡略化のため、exported ではないことを unexported と呼びます。
また、構造体の宣言やメソッドの呼び出しができるようになることを、本記事では参照可と呼び、できないことを参照不可と呼びます。

pakcage について

言わずもがな感じがありますが、package は以下のようになります。
./a/a.go./a/a2.goは同じpackage aに存在し、./b/b.gopackage bにのみ存在します。

.
├── a
│   ├── a.go
│   └── a2.go
├── b
│   └── b.go
└── go.mod

2 directories, 4 files

では、オブジェクト指向言語の対応と比較していきます。

本題

結論

オブジェクト指向のアクセス修飾子と Go の exported は以下の対応になることがわかりました。

Go の exported Go の unexported
オブジェクト指向の public 同一 -
オブジェクト指向の private - -
オブジェクト指向の protected - 同一

なぜ、結論のようになったのか、Go とオブジェクト指向をそれぞれ確認していきます。

解説

Go 言語において、pakcage 単位で exported によって参照可かどうかは以下のように決まります。

package exported unexported
同じパッケージ 参照可 参照可
違うパッケージ 参照可 参照不可

一方、オブジェクト指向言語において、package 単位で public・private・protected によって参照可かどうかは、以下のように決まります。

pakcage public private protected
同じパッケージ 参照可 参照不可(同一クラス内のみ参照可) 参照可
違うパッケージ 参照可 参照不可 参照不可

2 つの表だとわかりづらいので、1 つの表にまとめます。

package Go の exported Go の unexported オブジェクト指向の public オブジェクト指向の private オブジェクト指向の protected
同じパッケージ 参照可 参照可 参照可 参照不可(同一クラスのみ参照可) 参照可
違うパッケージ 参照可 参照不可 参照可 参照不可 参照不可

同じような性質のものがでてきたことがわかります。
具体的には、Go の exported とオブジェクト指向の public は同じで、Go の unexported とオブジェクト指向の protected は同一でることがわかりました。

さらに、まとめると以下になります。
Go にはオブジェクト指向の private が存在しないことがわかりました。

Go の exported Go の unexported
オブジェクト指向の public 同一 -
オブジェクト指向の private - -
オブジェクト指向の protected - 同一

このとき、初めて気がついたのですが、private に該当するものがないので Go では構造体の変数を隠蔽できなければ、final 修飾子もないので変数を不変にできないことがわかりました。

余談 internal pakcage について

Go の公開範囲のしくみには exported のほかに、internal package という概念が存在します。
書籍「プログラミング言語 Go」では、以下のように解説されています。

The package is the most important mechanism for encapsulation in Go programs. Unexported identifiers are visible only within the same package, and exported identifiers are visible to the world.
Sometimes, though, a middle ground would be helpful, a way to define identifiers that are visible to a small set of trusted packages, but not to everyone. For example, when we’re breaking up a large package into more manageable parts, we may not want to reveal the interfaces between those parts to other packages. Or we may want to share utility functions across several packages of a project without exposing them more widely. Or perhaps we just want to experiment with a new package without prematurely committing to its API, by putting it “on probation” with a limited set of clients.
To address these needs, the go build tool treats a package specially if its import path contains a path segment named internal. Such packages are called internal packages. An internal package may be imported only by another package that is inside the tree rooted at the parent of the internal directory. For example, given the packages below, net/http/internal/chunked can be imported from net/http/httputil or net/http, but not from net/url. However, net/url may import net/http/httputil.

net/http
net/http/internal/chunked
net/http/httputil
net/url

出典:The Go Programming Language 10.7.5 Internal Packages

パッケージは、Go のプログラムにおけるカプセル化のための最も重要な仕組みです。公開されていない識別子は同じパッケージ内だけで見えますし、公開された識別子はどこからでも見えます。
しかし時には、信頼された小さな集まりのパッケージから見えて、誰からでも見えるわけではない識別子を定義する方法という中間の立場が役立つでしょう。たとえば、大きなパッケージをもっと管理しやすい部品へ分解する場合は、部品間のインタフェースを他のパッケージへ公開したくないかもしれません。あるいは、プロジェクトの複数のパッケージを横断するユーティリティ関数を広く公開することなく共有したいかもしれません。あるいは、「試用期間」として限定されたクライアントにだけに提供することで、おそらく API を早まって確定することなしに新たなパッケージを試してみたいだけかもしれません。
これらの必要性に応えるために、パッケージのインポートパスが internal と命名されたパス部分を含んでいると、go build はそのパッケージを特別に取り扱います。そのようなパッケージはインターネルパッケージ(internal package)と呼ばれます。インターネルパッケージは、intarnal ディレクトリの親をルートとするツリー内の他のパッケージからのみインポートできます。たとえば、以下のパッケージ構成では、net/http/internalchunked は net/http/httputil や net/http からインポートできますが、net/url からはできません。しかし、net/url は net/http/httputil をインポートできます。

net/http
net/http/internal/chunked
net/http/httputil
net/url

出典:プログラミング言語 Go 10.7.5 インターナルパッケージ

最初みたとき、「protected に該当するのか」と思ったのですが、まったく役割が異なるみたいです。
一般公開されている Go のライブラリで、ライブラリ内で使いたいけどライブラリのクライアントからは呼ばれたくない場合に使われる印象です。

まとめ

  • Go の exported とオブジェクト指向の public・private の違いを確認した
  • 対応を確認し、Go は private に該当するものがない
  • internal package という概念も確認した

参考

https://www.maruzen-publishing.co.jp/item/?book_no=295039

https://www.gopl.io/

Discussion