Question

Why the default implementation of foo.pb.go use global registry, can I modify it to avoid namesapce conflict?

From the official doc says. It will meet a namespace conflict if I have a single proto file and using it generated two pb in different pkg. For example, example.proto is generate into two example.pb.go in pkg1 and pkg2 separately.

├─test_proto
    ├─main.go
    ├─example.proto
    ├─pkg1
       ├─example
           ├─example.pb.go
    ├─pkg2
       ├─example
           ├─example.pb.go

example.proto

syntax = "proto3";

package example;
option go_package = "./example";

message Greeting {
  string first_name = 1;
  string last_name = 2;
}

The command I use:

protoc --go_out=./pkg1 -I=. example.proto
protoc --go_out=./pkg2 -I=. example.proto

main.go

package main

import (
    "fmt"

    "Hello/test_proto/pkg1/example"
    pkg2proto "Hello/test_proto/pkg2/example"
)

func main() {
    pkg1Greeting := example.Greeting{FirstName: "foo"}
    pkg2Greeting := pkg2proto.Greeting{FirstName: "bar"}

    fmt.Println(pkg1Greeting.FirstName, pkg2Greeting.FirstName)
}

Now if I run main.go. It will panic with namespace conflict.

Exception has occurred: panic
"proto:\u00a0file \"example.proto\" is already registered\n\tprevious...

But if I modify one of the example.pb.go:

import "google.golang.org/protobuf/reflect/protoregistry"

    out := protoimpl.TypeBuilder{
        File: protoimpl.DescBuilder{
            GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
            RawDescriptor: file_example_proto_rawDesc,
            NumEnums:      0,
            NumMessages:   1,
            NumExtensions: 0,
            NumServices:   0,
            FileRegistry: new(protoregistry.Files), // add this line
        },
        GoTypes:           file_example_proto_goTypes,
        DependencyIndexes: file_example_proto_depIdxs,
        MessageInfos:      file_example_proto_msgTypes,
        TypeRegistry: new(protoregistry.Types),    // add this line
    }.Build()

Instead using default global FileRegistry and TypeRegistry. If I new a instance for it. The program will run smoothly. So why the default implementation must use the global Registry? Is it okay if I modify it?

 2  97993  2
1 Jan 1970

Solution

 0

Since the file starts with

// Code generated by protoc-gen-go. DO NOT EDIT.

it's probably not okay to modify it ;)

option go_package = "./example";

./example is not a valid go package. It should probably be

option go_package = "Hello/test_proto/pkg2/example";

with “Hello” being your module name. You'll seriously mess up descriptorpb.DescriptorProto, protoreflect and Any if you edit generated files.

Use different files when you need different structures. And use valid Go package names in option go_package.


Edit:

The package used in generated code, protoimpl warns: “This package should only ever be imported by generated messages. The compatibility agreement covers nothing except for functionality needed to keep existing generated messages operational. Breakages that occur due to unauthorized usages of this package are not the author's responsibility.”

You can use your own registry, but not with generated code. For example any.UnmarshalNew: “UnmarshalNew uses the global type registry to resolve the message type and construct a new instance of that message to unmarshal into.”

Or proto.UnmarshalOptions: “Resolver is used for looking up types when unmarshaling extension fields. If nil, this defaults to using protoregistry.GlobalTypes.”

Since this is called recursively, you can't mix types between registries, and you have to provide your own registry with every call.

I expect the breakage to be subtle at first, but you are clearly violating the assumption the generated code makes.

See also “How does the protoregistry.GlobalTypes load the types?” for strange results when types are missing from the global registry.

2024-07-09
eik