焼売飯店

GoとかTS、JSとかRubyとか

Protocol Buffersのpackageの名前解決ルールを調べた

Protocol Buffersのpackageの名前解決ルールを調べました。 このブログはproto3のドキュメントベースで書いています。

developers.google.com

protobufのpackageについて

protobufでは、messageなどの定義名が衝突することを防ぐことを目的として、名前空間を分離できる package と言う機構が提供されています。

説明を簡略化するために、本家のドキュメントを引用します。


Packages

You can add an optional package specifier to a .proto file to prevent name clashes between protocol message types.

package foo.bar;
message Open { ... }

You can then use the package specifier when defining fields of your message type:

message Foo {
  ...
  foo.bar.Open open = 1;
  ...
}

From: Language Guide (proto3)  |  Protocol Buffers  |  Google Developers


上記のように、importした側のprotoで、 foo.bar.Open と言った形で別packageのmessageを参照することが出来ます。

protobufの名前解決ルールについて

protobufのPackage名ベースでの名前解決は、初めに innermost scope から検索を開始する形で行われます。例としては、 foo.bar package から X messageの検索を始め、foo.bar package 内に見付からなければ、次は foo package 内に無いか探しに行くといった感じです。

https://developers.google.com/protocol-buffers/docs/proto3#packages_and_name_resolution

名前解決の具体例

foo
foo.bar
foo.baz

の3 packageがあった時、

  • foo.Fooは、foo.bar および foo.bazから、.foo.Foo, foo.Foo, または Foo としてアクセス出来ます。
  • foo.bar.Barは、foo.bazから、.foo.bar.Bar, foo.bar.Bar, または bar.Bar としてアクセス出来ます。

後者の方が若干ややこしいのですが、 innermost scope での検索なので、bar はスコープに入っておらず省略することが出来ません。

実例のProtoを貼っておきます。

foo/foo.proto

syntax = "proto3";

package foo;

message Foo {
}

foo/bar/bar.proto

syntax = "proto3";

package foo.bar;

import "foo.proto";

message Bar {
  foo.Foo foo1 = 1;
  Foo foo2 = 2;
}

foo/baz/baz.proto

syntax = "proto3";

package foo.baz;

import "foo.proto";
import "bar/bar.proto";

message Baz {
  foo.Foo foo1 = 1;
  Foo foo2 = 2; // OK

  foo.bar.Bar bar1 = 3;
  bar.Bar bar2 = 4;
  // Bar bar3 = 5; // NG
}

名前が衝突した時の挙動

前述した通り、packageベースの名前解決は innermost なので、最も内側のスコープで、importしたmessageと同じ名前のmessageを再定義することが出来ます。 上記の foo.baz packageの例で言うと、次のようなProtoが書けます。

foo/baz/baz.proto

syntax = "proto3";

package foo.baz;

import "foo.proto";
import "bar/bar.proto";

message Foo {
  string msg = 1;
}

message Baz {
  foo.Foo foo1 = 1;
  Foo foo2 = 2; // Redeclared in baz.proto

  foo.bar.Bar bar1 = 3;
  bar.Bar bar2 = 4;
  // Bar bar3 = 5; // NG
}

これを実際にコンパイルしたところ、 foo1foo2 で別々の型として扱われていることがちゃんと確認できました。

til/baz.pb.go at 68cce142b9e0b9a336fe65432238fea18b8206ce · syumai/til · GitHub

ひとまず今回確認したかったルールはここまでで、それはそうと言う内容ですが、ちゃんと説明できる程度に理解できたのでスッキリしました。 Proto周りのツールを連休中作っているので、完成したらまたブログで紹介すると思います! それでは皆さんよい連休を👋