Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

proposal: Go 2: add 'implements' compiler hint for interface implementations #28741

Closed
webern opened this issue Nov 12, 2018 · 8 comments

Comments

@webern
Copy link

@webern webern commented Nov 12, 2018

What version of Go are you using (go version)?

$ go version
go version go1.11 darwin/amd64

Does this issue reproduce with the latest release?

Yes.

What operating system and processor architecture are you using (go env)?

go env Output
$ go env
GOARCH="amd64"
GOBIN=""
GOCACHE="/Users/mjb/Library/Caches/go-build"
GOEXE=""
GOFLAGS=""
GOHOSTARCH="amd64"
GOHOSTOS="darwin"
GOOS="darwin"
GOPATH="/Users/mjb/repos/go"
GOPROXY=""
GORACE=""
GOROOT="/usr/local/go"
GOTMPDIR=""
GOTOOLDIR="/usr/local/go/pkg/tool/darwin_amd64"
GCCGO="gccgo"
CC="clang"
CXX="clang++"
CGO_ENABLED="1"
GOMOD=""
CGO_CFLAGS="-g -O2"
CGO_CPPFLAGS=""
CGO_CXXFLAGS="-g -O2"
CGO_FFLAGS="-g -O2"
CGO_LDFLAGS="-g -O2"
PKG_CONFIG="pkg-config"
GOGCCFLAGS="-fPIC -m64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -fdebug-prefix-map=/var/folders/rm/wb3nkcw91sl3174x90rw5n9r0000gn/T/go-build117438139=/tmp/go-build -gno-record-gcc-switches -fno-common"

What did you do?

I started programming with Go about a month ago.

It is truly an awesome language. However, I think a lot of time could be saved by adding an optional hint to the compiler when I am implementing an interface. When I, optionally, declare to the compiler that my struct implements a particular interface, the compiler should fail to compile when that struct has any missing/incorrect methods/signatures.

Example:

type Baz struct implements Foo, Bar {
    ....
}

In this case, Baz would fail to compile if any of the signatures of Foo or Bar are missing or incorrect, and the compiler could provide very specific information about the incorrect or missing signatures, at the site of Baz (instead of inferring my intent from a misuse of Baz).

This is a breaking change, but only if I choose to use it. If I want my code to compile with earlier versions of the language, then I can use this feature temporarily to help fix issues, then remove them for my final check-in. Tooling to remove these hints would be quite simple (it could be done easily with a Regex search and replace in Sublime, for example).

In short, having the option to declare my intention to implement an interface seems like it could only improve, not detract from, the language.

What did you expect to see?

Better information from the compiler and IDE about struct compatibility with interfaces.

What did you see instead?

Failures to compile because my struct is not compatible with the desired interface, at times without helpful information about why the struct is incompatible with the interface.

@gopherbot gopherbot added this to the Proposal milestone Nov 12, 2018
@gopherbot gopherbot added the Proposal label Nov 12, 2018
@webern webern changed the title proposal: add 'implements' proposal: add 'implements' compiler hint for interface implementations (2.0) Nov 12, 2018
@mark-rushakoff

This comment has been minimized.

Copy link
Contributor

@mark-rushakoff mark-rushakoff commented Nov 12, 2018

I think the convention here is a typed package-level variable assigned to the blank identifier:

var _ Foo = (*Baz)(nil)
var _ Bar = (*Baz)(nil)

If *Baz doesn't implement Foo or Bar, you'll get a compile-time error.

@webern

This comment has been minimized.

Copy link
Author

@webern webern commented Nov 12, 2018

I think the convention here is a typed package-level variable assigned to the blank identifier:

var _ Foo = (*Baz)(nil)
var _ Bar = (*Baz)(nil)

If *Baz doesn't implement Foo or Bar, you'll get a compile-time error.

Thank, I did not know about this convention yet, seems like it will help. Seems like a workaround for a missing language feature though!

@deanveloper

This comment has been minimized.

Copy link

@deanveloper deanveloper commented Nov 12, 2018

at times without helpful information about why the struct is incompatible with the interface.

What do you mean? It should say which function it caught that isn't implemented.

type Baz struct implements Foo, Bar { ... }

I have a few issues with this syntax:

  1. You are giving a special syntax for struct definitions. Notice that for a struct it would be type T struct implements I { <struct definition body> } (implements declaration in the middle of the full type declaration), but for any type that doesn't have a "body", the declaration would be type T []string implements I (implements declaration after the full type declaration). For consistency we would want the implements declaration always after the full type declaration, but that would look bad in my opinion.
  2. It does not allow for saying a pointer type should implement an interface
  3. It encourages defining structs around interfaces, rather than defining interfaces around patterns

Also, we have a way to do this already:

type T struct {}

var _ io.Reader = T{} // T should implement io.Reader
var _ io.Reader = (*T)(nil) // *T should implement io.Reader
@webern

This comment has been minimized.

Copy link
Author

@webern webern commented Nov 12, 2018

Despite working quite a lot with the language for about 6 weeks or so (like 200 hours!) I did not know about this convention var _ Foo = (*Baz)(nil). I'm half-way through Alan A. A. Donovan and Brian W. Kernighan's book, so maybe it's in there and I haven't gotten to it yet.

Perhaps then the proposal is to elevate this somewhat cryptic convention to a language feature.

Baz implements Foo
Baz implements Bar

Honestly the difference between pointer types and non-pointer types as it relates to interface implementations is one of the most confusing things I've encountered in the language thus far, so I can't really comment on that until I figure out how it works. There's ambiguity in my mind about what it means to be a pointer vs a non-pointer when I am an interface. And I haven't fully understood, when implementing an interface, what it means to have pointer vs. non-pointer receivers. I basically do one or the other and then go try combinations of & and * until it works. I'm mostly a C++ programmer, if that explains it.

In fact in the example:

type T struct {}

var _ io.Reader = T{} // T should implement io.Reader
var _ io.Reader = (*T)(nil) // *T should implement io.Reader

It is not currently clear to me why both of these would be necessary. It seems like one or the other would suffice. Is there some reading that would help me here?

In regards to 'encouraging defining structs around interfaces' that argument is a bit dogmatic for my taste. I guess there's some fear of Golang being used in way that looks too much like traditional OO?

@ianlancetaylor ianlancetaylor changed the title proposal: add 'implements' compiler hint for interface implementations (2.0) proposal: Go 2: add 'implements' compiler hint for interface implementations Nov 12, 2018
@ianlancetaylor

This comment has been minimized.

Copy link
Contributor

@ianlancetaylor ianlancetaylor commented Nov 12, 2018

I think that adding a new keyword to the language is a pretty heavyweight approach for an optional mechanism that can already be done in a different way.

@webern webern closed this Nov 13, 2018
@webern

This comment has been minimized.

Copy link
Author

@webern webern commented Nov 13, 2018

I was unaware of the convention to assign a struct to an unnamed instance of the interface.

@deanveloper

This comment has been minimized.

Copy link

@deanveloper deanveloper commented Nov 13, 2018

It is not currently clear to me why both of these would be necessary.

In terms of this, I meant more to show that you would do var _ io.Reader = T{} to assert T implements io.Reader, and var _ io.Reader = (*T)(nil) would be the equivalent for pointers

@webern

This comment has been minimized.

Copy link
Author

@webern webern commented Nov 13, 2018

It is not currently clear to me why both of these would be necessary.

In terms of this, I meant more to show that you would do var _ io.Reader = T{} to assert T implements io.Reader, and var _ io.Reader = (*T)(nil) would be the equivalent for pointers

Yes, I see that only one or the other is possible depending on which type of receiver the implementation has. I still don't understand pointer/non-pointer receivers and the semantics of the interface usage, but thankfully the compiler does! 👍

@golang golang locked and limited conversation to collaborators Nov 13, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
5 participants
You can’t perform that action at this time.