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

Already on GitHub? Sign in to your account

Proposal: Alias declarations for Go #16339

Closed
griesemer opened this Issue Jul 12, 2016 · 371 comments

Comments

Projects
None yet
Contributor

griesemer commented Jul 12, 2016 edited by robpike

Abstract
We propose to add alias declarations to the Go language. An alias declaration introduces an alternative name for an object (type, function, etc.) declared elsewhere. Aliases simplify splitting packages because clients can be updated incrementally, which is crucial for large-scale refactoring.

Motivation
During refactoring, it is often desirable to split an existing package into multiple packages. Exported objects such as types, functions, etc. that move from one package to another will require clients to be updated accordingly. In a continuous build environment, build breakages are the consequence if not all clients can be updated simultaneously.

This is a real issue in large-scale systems such as we find at Google and other companies because the number of dependencies can grow into the hundreds if not thousands. Client packages may be under control of different teams and evolve at different speeds. Updating a large number of client packages simultaneously may be close to impossible. This is an effective barrier to system evolution and maintenance.

Go trivially permits constants to refer to other constants, possibly from another package. Similarly, if a function moves from one package to another, a “forwarder” function that simply calls the moved function may be left behind so clients can continue to refer to the old package. These techniques enable incremental update of clients without breaking a continuous build system.

No such work-around exists for types and variables. To address the issue, we propose the notion of a general alias declaration. An alias declaration introduces an alternative name for an object (constant, type, variable, or function) declared elsewhere, possibly in another package. An alias may be exported, so clients can refer to on object via the package that declares the object, or any package exporting an alias to an object, and get the same object.

Alias declarations are designed such that they fit seamlessly in the existing language spec without invalidating backward-compatibility or any other aspect of the Go 1 guarantees. Tools that process Go code which will require changes to support alias declarations.

Alias declarations are a compile-time mechanism and don’t incur any runtime costs.

The design document (forthcoming) describes alias declarations in detail.

Added July 25, 2016: Link to design document: https://github.com/golang/proposal/blob/master/design/16339-alias-decls.md

@griesemer griesemer added the Proposal label Jul 12, 2016

@griesemer griesemer added this to the Proposal milestone Jul 12, 2016

@griesemer griesemer self-assigned this Jul 12, 2016

Contributor

griesemer commented Jul 12, 2016

nicerobot commented Jul 12, 2016 edited

Personally, just off the top of my head 👎 , I think I'm not interested in language features specifically targeting refactoring. I vendor libraries (using submodules or subtrees) for precisely this reason. That is, all my vendors can change their implementations at will without breaking my builds and my repos can uptake more current libraries as time permits.

In fact, i'm inclined to think this is a very bad idea because you can end up with aliases to libraries that further change their interfaces and break the aliases. For example, let's just say I ended up with 50 libraries that alias types in my library. How do I know about those dependencies? And if i choose to make a drastic change and privatize or rename the type, let's say i even gave a year notice of its deprecation. Do the aliases get that notification? How? And after the deprecation deadline, all those libraries that weren't changed now break.

CL https://golang.org/cl/24867 mentions this issue.

What is the significance of forbidding aliasing unsafe functions?
In particular, I see a conflict in reasoning: For unsafe.Pointer it is argued, that the same effect is already possible, so forbidding it makes no sense. But it is also possible to create a wrapper for a function in unsafe and thus effectively get the same effect (as outlined elsewhere in the doc), so it shouldn't make sense either.

Contributor

matttproud commented Jul 13, 2016 edited

I am confident this would have made a painful LSC refactoring I conducted several months ago more palatable. I appreciate your attention to these shortcomings.

One issue: how much is it worth to use -> given its similarity to the preexisting <- in channel operations? As someone who already knows Go, the potential ambiguity is not a problem. For newcomers, however, the cognitive impact could be profound.

(edited for clarity)

Contributor

cespare commented Jul 13, 2016

@matttproud Go does not already have ->, only <-.

If I may jump on the syntax bikeshed bandwagon, though, I think that => (mentioned in the proposal) would be slightly better than -> because it's a little further removed from <- and because it resembles =.

Contributor

josharian commented Jul 13, 2016

I'm inclined in favor of a more limited form of the proposal. Here's a first round of comments, in no particular order:

  • I'm concerned about the readability impact of aliased local variables (spooky action at a distance). Aliasing via pointers already incurs mental overhead; I wouldn't like to see more sources of that added. I'd like to suggest that this be restricted to top-level declarations. This addresses all the original motivations, while not adding aliases in places where code typically gets complex, i.e., in functions.
  • It was not so many years ago that I occasionally accidentally wrote val -> ch to try to send val over chan. We'd have to take special care to give very good error messages around this. This is an advantage of => over ->. (Not that I think it is a necessarily a good idea, but if restricted to the top level, then := could be used as the alias syntax.)
  • The gc compiler has special-cased goo to handle runes and bytes just for error messages. It runs through large swathes of the compiler (all the way up to and including Node->SSA conversion). I'm nervous that keeping aliases alive for the purposes of error messages will add non-trivial complication.
  • I wonder whether go doc and other documentation friends could automatically "follow" aliases if the alias itself doesn't have any associated documentation.
Contributor

dlsniper commented Jul 13, 2016

I can't speak yet if it's a good thing or bad (I lean towards bad but I need more time to understand the ramifications).

Meanwhile, I would see this as type from alias to instead of type from -> to or even type alias from to, that way it's at least explicit what that symbol means and it's not confused with channels / future channel ops(?)

Kunde21 commented Jul 13, 2016 edited

I don't like how closely the -> identifier resembles the <- identifier, especially with such different meanings. Might @ be a better option?

type  T @ L1.T   
const A @ L1.A   
var   V @ L1.V   
func  F @ L1.F   

Rather than reading the alias as "this points to", I see it as X is defined at L1.X.

Beyond that, I really don't like the scope that this proposal allows. Refactoring is a difficult problem, but it should really be driven by the package owner(s). To that point, I'd add these restrictions:

  • A single alias/offset pointing to the concrete definition, so we avoid alias chains. Jumping through 3+ packages to find the original definition would be frustrating and off-putting to those reading the code. Readability shouldn't be sacrificed during refactoring, as we know how permanent "temporary" tends to be. Also, I can't think of a time when aliasing an alias wouldn't add unnecessary complexity to the code.
  • Treat aliasing as we do internal packages. You can alias something from within your own code tree or your own organization's tree, but not outside. If I alias your type, I've created a brittle coupling that is hidden within my library. Dependency management is hard enough without these minefields.
  • At a minimum, go vet or golint should discourage using aliased definitions. Not only would this make dependent packages aware of the aliasing, it would encourage adoption of the desired refactoring.
  • Best case scenario, goimports is able to automatically update the alias and add the import. Adoption would be as simple as go get -u ./... && goimports -w ..

I definitely prefer this to import shenanigans.

Something like

type unexportedAlias -> pkg.T
func ExportedFunc(ua unexportedAlias) {}

seems like it could be confusing. Though obviously one should not do such a thing. Vet check?

What would the effect of

type unexported struct {}
type Exported -> unexported

be? That seems like it would have to be explicitly illegal.

As far as the syntax, I do like @ better than ->, very mnemonic. Totally fine with -> though—it'd stop feeling weird after a couple uses.

mem commented Jul 13, 2016

I think @Kunde21's point is an important one: since this is presented as a method to facilitate long refactorings, that implies it's addressing a temporary need. Yet at GopherCon one of the immediate questions was "can this be used to expose details of internal packages?" (I'd add "in a controlled manner") and that implies a more permanent situation. I'm not saying for or against, I'm saying that perhaps the motivation for this needs some refinement.

Contributor

josharian commented Jul 13, 2016

I presume that the following is illegal, but at first glance I don't see what in the proposal forbids it:

package foo

type T -> bar.T

func (t T) SomeMethod() { /* ... */ }

zellyn commented Jul 13, 2016

  • +1 (ish) on the proposal
  • +1 on allowing only at the top-level
  • agree that -> is confusingly similar to <-
  • <bikeshed>I like :=, although I've accidentally used that at the top level before as a beginner, and I'm sure the error message for that would become less clear :-) How about ==</bikeshed>
  • +1 on allowing exposing private things by aliasing. There's nothing magical about lowercased names, except that you're not legally allowed to type them from another package.

leighmcculloch commented Jul 13, 2016 edited

I agree with @Kunde21 @mem: while the motivation is to add this feature for refactoring I think it will likely be used more often in permanent code. I think @Kunde21's suggestions to have go vet discourage usage would be beneficial in preventing permanent usage, but since we're specifically talking about refactoring that will take a long time, we'd basically be ignoring warnings for a long time and we'd risk becoming decensitized to them.

Alternatives that come to mind that I did not see listed in the design document:

  1. Versioning – Is this problem something that versioning imports would resolve? When the public interface of a package changes in many other language ecosystems there's a bump in the major version number. The motivation statement gives the example of 100 clients. If each of these clients specified different versions of the import, then they could upgrade as they are able. This would still require upgrading an entire project at once, but at least not all projects at once.
  2. Renaming – Would it be practical to rename the package? i.e. Instead of L becoming L and L1, could L become L1 and L2? L would become deprecated.

(edited only to remove a duplicate word which made a sentence read poorly)

zellyn commented Jul 13, 2016

@leighmcculloch Versioning doesn't work. You need to actually alias, to keep type signatures compatible. Imagine if module M had RegisterHandler(f func(L.Foo)). Now you have to fork M too…

An observation: L1 can't import L while L aliases one of the symbols in L1, at least without creating a circular dependency. I think this limits the usefulness of the proposed feature, but perhaps only marginally?

Contributor

ct1n commented Jul 13, 2016 edited

How about:

type T = L1.T
const A = L1.A
func F = L1.F
var V = @L1.V

This would, at least conceptually, lead to things like:

var px, ax = &x, @x

(does this go towards pros or cons?)

If a thing is not exported and you want to export it, just change it's name to uppercase. Aliases seem neither helpful nor sensible to me, to "export unexported things". Same goes for aliasing into internal packages - by definition you have full control over both the internal subtree and everything that's using it, so if you want to export something from an internal package, don't put it into internal/.

For those reasons I think it's not a good idea to allow exporting unexported things; either giving a compilation error in the package that defines the alias (in the case of unexported identifiers) or in the package that uses it (maybe? In the case of internal packages. Because "using an alias into an internal package is the same as using the aliased identifier", so it should give an import error?), or also the package that defines the alias (probably less confusing).

atdiar commented Jul 13, 2016

Just a few quick comments before I try to think it over a little more:

  • The alternative is for people to export constructor functions when they need types to be exported transitively through the package importing graph. This proposal could ease the process, however.
  • The syntax introduces new sigils, that are actually used for chan. It is a bit annoying
  • It will not help with vendoring type equality issue (actually not an issue, that's just how vendoring works). I think it is important to highlight that.
  • It is closing on the "functor" mechanism that can be found in OCaml to the best of my knowledge. (the paragraph on import)
  • As a beginner programmer, (I started programming back in Go after a ten year hiatus) I initially desired such a feature. As I am more experienced now, I haven't felt a need for it, at my somewhat micro-scale. On the one hand, except for the syntactic issue, it should be fine for a beginner to comprehend. On the other hand, one should make sure that it is truly needed.
    In fact, I think it might allow bad idioms to appear. Constraints make things clearer.
Contributor

josharian commented Jul 13, 2016

Yet at GopherCon one of the immediate questions was "can this be used to expose details of internal packages?" (I'd add "in a controlled manner") and that implies a more permanent situation.

The discovery that there are uses for aliases other than the original motivating examples speaks in favor of the proposal, not against it. It means the proposed mechanism is general and powerful.

I find this second application appealing. Note that we already have a similar mechanism for 'go test' that provides a similar feature, namely exporting a variable/function only when testing. (See any of the files named "export_test.go" in the standard library.) It is very useful.

Contributor

josharian commented Jul 13, 2016

And that observation points out an existing way to accomplish the refactoring goals (I think) without changing the language: build tags. Type T could live in package L or L1, depending on the presence of a build tag. When clients migrate, they flip their build tags. Once all clients have migrated, all build tags get removed.

This adds several burdens.

  • The library maintainer must juggle the build tags, which is harder than adding aliases. Nevertheless, it seems workable.
  • The client libraries that have upgraded must now remember to provide build tags on every invocation of the Go tool. This pushes developers towards makefiles. This could be addressed with additional support from cmd/go, though. For example, one could add a magic comment next to imports indicating which build tags to use when compiling that import for use:
import "pkg/path/L" // +build v2

There are obviously wrinkles here. For example, the build tags for an import must appear only once (or must not conflict), but that kind of situation already exists for the custom import path restriction comments. There are other build tag persistence mechanisms available; this is just to illustrate. (Please don't bikeshed with new ideas for build tag persistence here. That would be a separate proposal. The important point is that there are options.)

  • godoc and other tools have marginal support for build tags and build restrictions. For example, https://godoc.org/syscall is of little use to developers on Windows. Tooling improvements might be needed.
  • If there are multiple migrations occurring at the same time, you could get combinatorial build tag explosion. Not sure what to say about that other than "don't do that".

I'm not sure where I stand on aliases vs build tags, but it seems worth exploring a little.

Contributor

natefinch commented Jul 13, 2016

I see two drawbacks to this feature:

1.) It breaks the "different names means different types" invariant that has existed since Go 1.0.

type foo struct { ... }
type bar => foo  

bar is foo. It's not just the same shape in memory as foo, it's not just compatible with foo's methods. They are the same type. Something that takes a func(foo) error can take a func(bar) error. Before now, this was impossible, and I think that was a good thing.

2.) When this is used in practice, the aliases will almost certainly live on forever. The whole reason you'd use an alias is because the refactor would be too cumbersome without it and/or you just don't control the other code referencing these types. This means that large swaths of the codebase will be referencing these aliases, and then any new code has to make a choice - use the new "real" location of the types, or use the aliases to conform to the way the rest of the codebase looks.

In practice, I think this feature will lead to confusion, since this is mostly useful for large codebases with lots of developers, and developers will constantly trip over two pieces of code that look like they're using different types, but really are not. For an example, consider how many people have asked how to convert int32 to a rune, or a string into a []byte (the latter is not a perfect example, but close enough for illustration).. and those are built into the language.

However, I'm not totally against it. This would have been hugely useful in one specific case I had in Juju. I had a to do a massive refactor to move a very commonly used package outside of the main Juju repo for use in a separate repo. The work was delayed by months because I had to touch so many files that I inevitably had a million conflicts every time I updated from master. If all I had to do was make an alias in the original place, all those conflicts would not have happened, and in theory, we could have done the conversion incrementally (though, see number 2 above, I don't think we actually ever would have put forth the effort).

Contributor

jessfraz commented Jul 13, 2016 edited

TLDR I'm a -1.

So I have some thoughts we discussed in person but I thought I would put them here to make sure they are taken into account by all members:

1.) I really think with the current proposed implementation this could be easily abused.
2.) If 1 is correct, any way that someone uses "aliases" outside the intended context will need to be supported forever, and maybe we all have no idea what those may be now, they will come to light when hypothetically it breaks. We saw this happen a lot on the docker project and it's super unfortunate when it stops development of something else.
3.) Seems like the main use case for this was for "refactoring" and to be honest I really think that only applies to super large scale companies and I think in any other context using it for refactoring would kind of be like "cheating".
4.) It is intended for "refactoring" but nothing is stopping people from using it right out the gate of a new project for... reasons? giggles? idk. but people will do it.

cc @kelseyhightower who also had some thoughts I can't express as eloquently as he can

Contributor

neild commented Jul 13, 2016

For sufficiently large codebases, type aliases aren't just a convenience. It's effectively impossible to refactor types without them.

It's not uncommon for us to make changes to packages which are imported by hundreds or thousands of files spread across many different projects. The usual process when making an incompatible API change is to initially support both the old and new interfaces, gradually update all the uses of the old interface to the new one, and finally drop the old interface. We've got tools which automate parts of this process.

It is impossible to move a type from one package to another using this approach, however, since it is not possible to have a transitional period where the type exists in both places..

Build tags as suggested by @josharian are unfortunately not a solution, since they would require that all the components of a program agree on a single location for the type.

The inability to move types in widely-used packages is causing us ongoing pain, and I don't see any way to resolve it without some form of type aliasing.

Contributor

griesemer commented Jul 13, 2016

Thanks to everybody for providing this extremely valuable feedback. This is exactly what we (the Go team) were hoping for. Please keep it coming; especially if you have specific comments that have not been voiced so far. Please use the +1/-1 emoticons to express general support or disagreement. Thanks.

Here is some feedback regarding the comments so far.

@Merovius: The unsafe functions are actually built-in functions and we also disallow aliasing for built-in functions. Some of them (in fact all the unsafe built-in functions) have a "generic" signature. If it were possible to alias them, they could potentially be exported, and that would require special export/import machinery since they don't have a signature that can be expressed in Go. It does not seem worthwhile adding complexity for a mechanism that is questionable at best (aliasing unsafe functions). The reason we allow aliasing for unsafe.Pointer is that it's already possible to define a type that has unsafe.Pointer as underlying type. This is all outlined in the design doc.

@matttproud, @cespare, @josharian, and others: A lot of people have raised concerns about using -> and would prefer => instead. I'll adjust the design doc to use => going forward.

@josharian: It is probably a good idea to restrict alias declarations, at least to start with. We can always remove restrictions down the road if necessary. I am inclined to say we should allow alias declarations for imported objects only (so no local aliases). Restricting them to be placed at the top-level only would be a secondary restriction in my mind. I'm not sure it's needed if we have the former restriction.

@josharian: I believe you're right that it's going to be a bit tricky to get the compiler to give good error messages in the presence of aliases. But that's mostly an implementation detail. There are probably also a lot of issues that go doc and friends need to handle. That said, I believe this is something that can be refined over time w/o impacting the fundamentals of the proposal.

@dlsniper: Your suggestion of writing type from alias to would require alias to be a keyword. As discussed in the design doc, introducing a new keyword is generally not possible, though it would be doable at the top-level; however not in the position you are suggesting without what I would call a "gross hack". But I agree that using -> is perhaps not the best choice and => might be better.

@Kunde21: We have spent several hours playing with different notations, and @ was one of them, as were ~, ~>, ~~, ==, ===, and probably a few more (= really would be perfect if we could use it). Let's go forward with => for now, but keep @ back in mind since others also like it. It's trivial to change the token down the road if people clearly start leaning strongly towards a specific one.

@Kunde21, @mem: Regarding chains of aliases: That is a very valid concern. We should perhaps start with disallowing aliases to aliases. It's a simple, easy to understand restriction, doesn't limit the proposal much, and can be trivially lifted if need be down the road.

@Kunde21: Allowing aliasing only internally defeats the purpose of the proposal (but perhaps I misunderstood your comment).

@Kunde21: There's a lot we can do with go vet/lint, goimports, and friends. Let's discuss this as a separate issue.

@jimmyfrasche: If we restrict aliasing to imported objects only, I believe your concerns are addressed. That is, an alias can only export something that was exported before (since it can only alias something that was imported).

@mem: Using aliases to export objects of internal objects seems like a very important alternative use case. This would still be possible to do if we only permit aliases to exported objects.

@josharian: Adding methods to a type via its alias name in the receiver specification seems like it should be possible if the alias refers to a locally declared type since the alias really just means that type. It should not be legal if the type is imported (it's not legal now). But it we restrict aliases to imported objects only, this question becomes moot.

@zellyn: I think := instead of -> or => would be even more confusing given its existing use.

@zellyn: Several people have expressed concerns abut exposing private things via exported aliases. Let's start small.

@leighmcculloch: I believe your questions have been answered by others. I'm not sure versioning is helping here (see @zellyn's feedback). Renaming of packages may work in some cases but not others.

@stephens2424: You are correct that one has to be very careful about not creating circular dependencies. I have hinted at that when discussing the work-around for variables. I think aliasing will make it possible to avoid circles which might occur if only other work-arounds were available.

@perses: We did think about a special notation for variables only; including the possibility of making @a more generally useful (in fact, we internally discussed the very same notation). That is, @x could mean a something along the lines ofreference to x without using pointer notation. But now we have two mechanisms to discuss: aliasing, and "variable references", and the latter seems to open an even bigger Pandorra's box. Let's not go there in this discussion.

@Merovius: Agreed. There is strong sentiment in favor of not allowing aliases to export unexported objects.

@atdiar: Thanks for your feedback. I believe many of your concerns have been brought up by others as well. Perhaps you can elaborate on the relationship to the "functor" mechanism of OCaml (with which I and probably many others are not familiar with).

@natefinch, @jfrazelle: Your concerns have been echoed by many people. I think we do need to start with a smaller and restricted proposal and take it from there. I will update the proposal to take this feedback into account.

That restriction fixes my second concern, but, even with that restriction, you could create an unexported alias of an exported type at top level.

If you have

package B
import "C"
type unexported -> C.Exported
func Exported(v unexported)

and you import B from your package A, the rules available for construction of a value of type unexported change based on whether you also import C, which gives you direct access to C.Exported, but unexported won't show up in godoc so it changes silently.

A lot of bad decision dominoes need to fall to get into that situation, but it seems like a recipe for confusion and edge cases.

Kunde21 commented Jul 13, 2016

@griesemer To clarify my suggestion about restricting internally, this would only be for defining an alias.

If we're moving type OldType from package vcs/user1/library/subpkg, we could create an alias:

// Within the same repository
import "vcs/user1/library/subpkg2"  
type OldType => subpkg2.NewType

// Or within the user's tree
import "vcs/user1/newlib"
type OldType => newlib.NewType

Consumers of the library would still see:

// Unchanged from before the alias definition
import "vcs/user1/library/subpkg"
var myvar subpkg.OldType  

What I'm looking to avoid is allowing anyone to create aliases into someone else's code. This would still require users to fork the base library, in order to refactor a type out of it:

// package vcs/user1/mylib
import "vcs/user2/YourLib"
type MyType => YourLib.YourType  // Error:  Alias can't be created.
Contributor

natefinch commented Jul 13, 2016 edited

@Kunde21 I'm not sure I understand why you'd want to prevent someone from making an alias into "someone else's" code. The concept of code ownership in this case seems irrelevant. The person making the alias is not in any way changing the target package. At worst they're giving consumers a different way to write the name of the thing from that package.

It's like named imports...

import natesucks "gopkg.in/natefinch/lumberjack.v2"

Sure, now you can reference things in my package as natesucks.whatever... but... so?

Kunde21 commented Jul 13, 2016

@natefinch The stated motivation for this proposal is to aid in refactoring. A bunch of aliases to the same type across codebases (users/teams/organizations) isn't in keeping with this goal.

Additionally, we want to avoid chains of aliases. As soon as vcs/org2/lib creates an alias that points to vcs/org1/lib.Type, org1 can't refactor lib.Type using an alias without immediately breaking vcs/org2/lib. The very thing this proposal is looking to avoid.

atdiar commented Jul 13, 2016 edited

@griesemer re.OCaml functors, a functor is a module that is parametrized by another module, just like a function is a value which is parametrized by other values, the arguments.
Since an alias is a reference to an object from another package, it might be interesting to ask how it is handled there. But to my knowledge this is one of the not-so-trivial feature of OCaml

The thing is that the proposal is quite correct when one needs to separate a package into different packages. I expect that the goal is for reuse by multiple client packages because otherwise, one would just need to split a package into different files.

Where I would be cautious is that different client packages would then be allowed to use different aliases. Because types can be exported, aliases, even if not aliasable themselves, should be exportable. I'm worried that it will muddle the readability of function type signatures at some point in the import graph (because different client packages can each define their own aliases..and then merge diamond-style).

Contributor

josharian commented Jul 13, 2016

I am inclined to say we should allow alias declarations for imported objects only (so no local aliases).

This seems like a fine restriction and would address my primary concerns.

Contributor

griesemer commented Jul 13, 2016

@Kunde21: Even if one wanted to, there's no general way one could prevent somebody from making an alias into somebody else's code. (I don't know how you would identify code as someone else's code for the compiler).

Contributor

natefinch commented Jul 13, 2016

I think @Kunde21 's point about aliases actually brings up a good point.... I don't think we should prevent aliases to aliases... because then if someone has already aliased your type, you could inadvertently break them by aliasing it yourself... when the whole point of your alias is not to break your consumers. I'm not sure it's worth the headache just to avoid an alias to an alias, which is perfectly understandable (if annoying when navigating sourcecode).

Kunde21 commented Jul 13, 2016

@griesemer: Similar to the restriction on importing internal packages, I see this being enforced by the go tool. The enforcement logic is similarly based on the import path, as well.

Contributor

griesemer commented Jul 13, 2016

Please keep in mind that even without aliases, already now we can do some of the things some people have been objecting to, at least for constants and functions:

One can trivially write an exported wrapper function that calls an un-exported function and thus provides (indirect, but still) access to the un-exported function and vice versa. Same is true for constants, simply via a constant declaration. The main difference here is that (for functions) alias declarations require less to write (but even that is not quite true if the function is "aliased" via a function variable that is initialized to the function to be aliased).

The only place where aliases provide true new functionality is for variables and types. Furthermore, even for variables there is a work-around (if tedious) if the problem is refactoring.

(Thus, a "minimal" proposal could be restricted to aliases for types only, and then we could even use the notation type T = imported.T. This specific notation and idea has been in the back of our minds for a long time. The alias proposal is simply a generalization.)

There are also two important use cases for aliases that have nothing to with refactoring:

  1. Building components (as has been brought up before): A large library could be organized in different packages which export objects as necessary so that they are accessible within the library. All these packages would be in an internal directly, effectively hiding them from the "outside". At the top-level there is a single package which implements the public interface of the library. Alias declarations can be used to re-export the objects that need to be accessible publicly. (Right now we cannot do this for types that are not declared at the top).

  2. Remove need for dot-imports: Dot imports (import . "pkg") are generally frowned upon (there are some rare use cases related to test package creation with go test). Alias declarations could be used instead of dot-imports and would provide a much clearer picture of the identifiers accessible in a package.

atdiar commented Jul 13, 2016

  1. Let's say pkg A defines type Foo
  2. pkg B imports A and alias Foo as Bar.
  3. pkg C imports A and alias Foo as Baz.
  4. Finally pkg D import pkg B and pkg C and uses Bar and Baz.

If this scenario is possible with the current proposal, it will probably impede the readability.
Having different aliases for a same object could be annoying.

Contributor

griesemer commented Jul 13, 2016

@Kunde21 I think this would be difficult to enforce by the go tool. The go tool can enforce the imports because the meaning of import paths is not defined by the language (it is an externally agreed upon convention). As a consequence, the mapping of import paths to actual files is controllable from the outside of the compiler.

Alias declarations on the other hand operate on compile-time internal objects only. It would be pretty intrusive to impose outside restrictions to the compiler at this point.

Contributor

griesemer commented Jul 13, 2016 edited

@atdiar Yes, that would definitively be possible with the proposal. Keep in mind that I can do this already with functions:

  1. Say pkg A defines a function Foo.
  2. Pkg B imports A and exports a wrapper function Bar that just calls Foo.
  3. Pkg C imports A and exports a wrapper function Baz that just calls Foo.
  4. Finally, package D imports B and C and calls Bar and Baz

And of course that's possible with constants trivially. I think it's just that aliases make this a bit easier.

atdiar commented Jul 13, 2016

Are aliases of a same object interchangeable?
My assumption was to say yes.
In your example, Bar and Baz are still different functions.
With aliases, I am not sure they would /should be.

Contributor

dlsniper commented Jul 14, 2016

After thinking a bit about this I think I'm not getting the full picture so maybe you can help me out a bit.

From my understanding, this issue comes in from the fact that variables and types can't be easily changed from L to L1, correct?

I've seen a proposal to relax the rules about the interfaces, which would help a bit.

After moving all from L to L1 I would solve these issues like this:

  • introduce functions instead of variables in L so that instead of calling L.var you call L.Var() and L.var = 1 becomes L.SetVar(1)
  • types can be aliased as well since you can have type MyStruct struct{} as type type MyStruct L.MyStruct which can be converted later on in L if passed as arguments then forwarded to L1

If I understand this correctly and the assumptions above hold, I see no reason why writing a simple tool called go tool alias L1 L that would basically write a "shell" of the input package and turn it into the above rules.

From my basic understanding of this, all the problems come from the rather unique mono-repo approach Google has (and I don't know how versioning works). I'd be interested to know if versioning of packages would be a suitable solution for the problem (migration at different speeds). Could it be that Google is the only one facing this problem and in this case, rather than change the language other means to solve the problem can be explored?

Finally, from a readability point of view, which is the first thing any Gopher would say about the features of Go, this looks to be in direct conflict since it would mean that I'm looking at a type which is not what I think it is. It also leaves a lot more room for abusing than the above solution since the above solution would force people into thinking how their package API looks like versus having such a convenience. And like someone else already said, some of these aliases might need to be maintained for a long time, which brings us again to the question about: isn't this better solved with versioning rather than aliasing?

Sadly I don't have the experience of large scale refactoring as mentioned but I'd be curious if I'm missing something from this?

Thank you for your patience.

Contributor

ianlancetaylor commented Jul 14, 2016

@dlsniper Your suggestion for type MyStruct L.MyStruct does not work, because the new type does not have any of the methods of the original type. This is made more clear in the doc.

Contributor

dlsniper commented Jul 14, 2016

@ianlancetaylor true, but those could also be generated as stubs for the "real" methods like in this example: https://play.golang.org/p/D_voJ8086P (sorry I wasn't clear enough).

Contributor

griesemer commented Jul 14, 2016

@atdiar An alias is just an identifier denoting an object (const, var, type, func). The alias name and the original name denote the exact same object and thus are fully interchangeable. Think of it like two labels attached to the same thing.

You are correct that a wrapper function (the "alias") is not the same function as the one it calls and thus not quite the same as an alias. But the effect is the same: No matter which function you call, you get the same result. Furthermore, because equality is not defined for functions (only testing against nil), a program cannot observe the difference between the two functions (excluding the possibility that the wrapper might run slightly slower if the call it wraps doesn't get inlined).

The same is true even more so for constants.

Contributor

griesemer commented Jul 14, 2016 edited

@dlsniper Your are correct that there are work-arounds for variables (as I have outlined in the GopherCon lightning talk as well as in the design doc). It can work for refactoring, though it may need some careful planning.

The problem with types is that even if you attach the right methods, it's a different type from the original type. In contrast to the work-arounds for everything else, this difference is observable by a program: The original and the new ("fake alias") type are different and variables of these types are not assignable to each other: https://play.golang.org/p/nQUgiuJhMn

That said, casting would work in those (assignment) cases as well, which is an interesting opening to a work-around. However, casting doesn't work if the (fake) alias and original type appear in derived types (see bottom of https://play.golang.org/p/nQUgiuJhMn). Casting never goes inside the underlying types.

Thus, your suggestion may work in many cases and may be done fully automatically with a clever tool. But it cannot work in general. Again, the reason is that the type difference is observable by a program and there's not even a way to "make the difference go away" (via a cast) in general.

Which brings me back to the "minimum" alias proposal I have hinted at briefly in one of the comments above, which is alias declarations for types only.

atdiar commented Jul 14, 2016

Ok so now I see the advantage of the proposal.
I am just a it worried of situations where we would have in a package:

someFunc := func( b Bar) int{ return 2}

This function could accept many other aliases as argument but it might not be clear enough to the package user.

Unless I missed a detail.

Contributor

mibk commented Jul 14, 2016

We did think about a special notation for variables only; including the possibility of making @a more generally useful (in fact, we internally discussed the very same notation). That is, @x could mean a something along the lines of reference to x without using pointer notation. But now we have two mechanisms to discuss: aliasing, and "variable references", and the latter seems to open an even bigger Pandorra's box. Let's not go there in this discussion.

I was thinking about something like unsafe.Reference(x) that would mean a reference to x without using pointer notation. That way it might be clearer that it's not intended for regular usage, although it's probably not clear enough.

Being the proposal one way or another (make aliasing available for all const, var, func, type, or just for type), I think it is important to come up with a solution that makes it possible to mark all const, var, func, type as being deprecated as part of some refactoring process (either using the proposed special syntax => or by making some kind of //go:comment with a special meaning) to enable some go tools (not necessarily goimports) to automatically refactor the deprecated stuff as was mentioned by @Kunde21.

In general I'm in favour of the proposal.

Maybe the following is outside the scope of it.

I think it is important to come up with a solution that makes it possible to mark all const, var, func, type as being deprecated as part of some refactoring process (either using the proposed special syntax => or by making some kind of //go:comment with a special meaning) to enable some go tools (not necessarily goimports) to automatically refactor the deprecated stuff as was mentioned by @Kunde21.

I think it's a good idea to have a way to mark a const, var, func or type as deprecated. That is useful for signaling clients of packages. This might be more the case in manual refactoring and maybe comes in to play with versioning of a package. In the case of automatic refactoring, I think having deprecation messages is not a good feature because one might tend to ignoring them.

I'd like also to note there is still gofmt -r and eg.

Contributor

ct1n commented Jul 14, 2016

There's just one point I'm curious about: if you decide to make @a more generally useful in the future won't the notation using => become superfluous?
If you restrict the @a variable notation such that var blocks containing one must only contain aliases and they can only point to imported variables, won't the semantics be the same but the syntax future-proof?
Sorry to bikeshed. To be honest I'm partial to the equals notation.

Contributor

griesemer commented Jul 14, 2016

@perses, @mibk: The @a would be effectively create a user-defined reference type. Related to the proposal insofar that it would allow a more natural work-around for variables. But it's yet another mechanism and we'd still need alias declarations for types. Also, we made a deliberate decision to make user-defined pointer types require explicit *s. I mentioned it only for completeness, it's not something we'd seriously consider.

Contributor

griesemer commented Jul 14, 2016 edited

@atdiar: I'm not clear on what you meant with your last function question. Keep in mind that already now, a programmer can create aliases to things: For instance, two pointers may point to the same object.

Another thing to keep in mind: While alias declarations introduce more names for an object, the property that one can trace back the declaration of anything by following the declarations backward is still true: For any alias name in use, there is an explicit declaration, and that declaration leads back to the origin.

In fact, the only place where this is currently not the case is with dot-imports: A dot-import populates the file block with names but one never sees an explicit declaration in the code. Aliases could be used instead of dot-imports and actually make this much clearer.

atdiar commented Jul 14, 2016 edited

@griesemer Two pointers will have the same exact type name.

With the alias proposal, since the aliases are interchangeable, a function may accept an arbitrarily large number of values whose type names are "seemingly" all different although they all actually refer to the same type.

Multiplying that effect by the number of functions, struct fields etc, in a package, I am a bit wary of it becoming unruly. Particularly in the diamond case mentioned earlier where the aliases are exported.

Contributor

abhinav commented Jul 14, 2016

+1 to this proposal.

Another motivation for aliasing internal objects:

If you have,

outer/
    |- inner/
        |- bar.go
    |- foo.go       // imports outer/inner

Where package outer implements the primary API of your library and inner
provides a lower level API which outer builds upon.

You can't define a type T in outer that can be used from inner because
that introduces an import cycle. So you will either define it in inner or
in another subpackage.

outer/
    |- another/
        |- t.go     // type T struct{..}
    |- inner/
        |- bar.go   // imports outer/another
    |- foo.go       // imports outer/another

This is undesirable if you want to export T as part of your primary API,
while still having access to it in the low-level API.

We have worked around this by defining an internal type and creating new
declarations of it in inner and outer:

outer/
    |- internal/
        |- t.go     // type T struct{..}
    |- inner/
        |- bar.go   // type T internal.T
    |- foo.go       // type T internal.T

The aliased versions have copies of the internal type's constructors and
methods that cast to and from the internal type. Plus the outer version
casts to the inner version when calling the low level API.

func NewT() T {
    t := internal.NewT()
    return T(t)
}

func (t T) Get() string {
    return internal.T(t).Get()
}

With this proposal and support for aliasing internal objects, all this
boilerplate and casting goes away.

outer/
    |- internal/
        |- t.go     // type T struct{..}
    |- inner/
        |- bar.go   // type T => internal.T
    |- foo.go       // type T => internal.T

CC @prashantv

Contributor

griesemer commented Jul 15, 2016

@atdiar Yes, there is a chance of significant misuse as with any programming construct; but perhaps more so with aliases. I suspect tooling could help quite a bit: For instance, go lint could fairly easily point out the use of different names for the same type in a given package.

Contributor

griesemer commented Jul 15, 2016

@abhinav : Thanks for pointing this out explicitly with examples. This is exactly the scenario I mentioned as another use case for aliases: building components consisting of many packages, with one package using aliases to export the component's API.

In fact, this may be more prominent a use case than refactoring.

Contributor

griesemer commented Jul 15, 2016

@josharian Regarding build tags: You raise a good point. There are probably many situations where build tags can be used in refactoring situations.

I probably didn't make a strong enough point for the other use cases of alias declarations that I consider important:

  1. Use of aliases to export the API of a component consisting of many packages (brought up several times so far).

  2. Potential use as replacement for dot-imports.

  3. Support for the "import public" feature in the Go implementation of protocol buffers. See: https://developers.google.com/protocol-buffers/docs/proto#importing-definitions for more details. I have not discussed this at all so far. I will outline this in more detail in the design doc.

@gopherbot gopherbot pushed a commit to golang/proposal that referenced this issue Jul 18, 2016

@griesemer griesemer design: Alias declarations for Go
For golang/go#16339.

Change-Id: Ie8b17a08cccaf8ef8e0f3d0846d94a5a2d693f10
Reviewed-on: https://go-review.googlesource.com/24867
Reviewed-by: Josh Bleecher Snyder <josharian@gmail.com>
1487446

Personally i'm in agreement with the wider community in not wanting new language features that impede readability. I do however have a question about how aliases would work. Would it be possible to alias stdlib types and access their unexported fields? I wonder if this could lead to unpredictable behaviour.

Apologies if this has been answered already, i've not had time to read the whole thread in detail.

Member

c4milo commented Oct 28, 2016

With vendoring already part of the standard tooling, are alias declarations still relevant or needed?

zellyn commented Oct 28, 2016

@c4milo scroll up for me repeatedly pointing out how vendoring and aliases are (mostly) independent, and eventually giving up.

@arussellsaw No, it's not possible. Usage of an alias is equivalent to the usage of the aliased type. In particular, you can not use unexported fields of types of a different package.

wsc1 commented Oct 28, 2016

This comment is about a very big picture observation:

  • Aliases are proposed to aid refactoring
  • But aliases themselves are also subject to refactoring
    For example, an alias could be renamed to fit a naming convention.

Then refactoring becomes in a sense more difficult just by virtue of the existence of aliasing.

In short, in does not make sense to introduce a language feature to simplify automated treatment of language (for refactoring), because it makes the language more complicated.

Although perhaps some examples may be "easier" with aliasing, there are also examples where aliasing would make similar tasks more involved, like refactoring an alias.

wsc1 commented Oct 28, 2016

@rsc I'm not at all opposed to implementing it and trying it. However, the objections in my view are not the sort that will show up as major problems in the proposed try-out timeline. They are longer term -- readability after aliasing is used on top of aliasing and inevitably sometimes abused, slowing tool (and editor) support by adding complexity to the language, etc. These are not things that are likely to produce a stopper with a try-out, but they are legitimate concerns.

I

pciet commented Nov 2, 2016

Despite acceptance as a language feature, aliases are not required in the compiler or standard library is my understanding, but a commit with the feature has already been included in the 1.8 standard library.

If at this point the language maintainers decide to continue only accept alias-free code for standard library implementations then the feature is optional for those that don't want it in their project(s) through a parallel branch (fork?) with the alias compilation code removed.

If implemented in this contained way the public Go community I expect could easily maintain such a parallel branch without continued Google or standard Go maintainer involvement.

NDari commented Nov 3, 2016 edited

rsc posted a very detailed answer about the concerns raised about this feature elsewhere which I quote below as I think it it good to have it all in this thread.

rsc said:

"...
We certainly hear the people saying that they think this is making code less readable, or can or will be abused. To those people I can only ask for patience to let this process play out.

We all have a natural bias to see "different" as "unreadable". I remember very clearly the discussion in Rob's office in January 2009 in which we - Rob, Robert, Ian, Ken, and I - decided to try "upper case means exported" for symbol visibility. I personally thought it was not a good idea and hurt readability, but the proposal addressed a real need (selective export of struct fields), and I could make no technical arguments against it other than I didn't like the way it looked. We did of course try it, and after a few weeks it stopped looking unreadable (that really meant "less like C than Go had before") and became completely natural. Today it is one of my favorite Go features, and I don't think anyone here would argue that it makes the code unreadable. To the contrary, there is actually a technical argument that it enhances readability. I realized this a few years later, when I had to do some work in an unfamiliar section of Google's C++ code base. I found it quite frustrating that I couldn't tell just from looking at a use of a method whether this was a public or private method. I'd see a call and think "wow, is that part of the public API? That would be scary." and have to go dig up the actual method definition and see that oh, it's marked private. In Go, that information is visible at each use, and I'd grown accustomed to having that information implicitly at hand when reading code. So not only was I wrong about the aesthetics of "upper case means exported" - we all adapted just fine to reading Go code written that way - but if we'd let the aesthetics make the decision, we would have cut off this other technical benefit, which we certainly discussed at the time but I think could not fully appreciate the value of until later.

Every language feature can be abused. The fact that Go uses predeclared identifiers instead of keywords when possible makes it easier to extend the language with new builtins without fear or breaking existing programs, like when we added delete. It also lets code dealing with marshalling or unmarshalling have methods with clear names like int and string (see for example go/src/encoding/gob/debug.go). These are good things. It also lets people write code like https://play.golang.org/p/3wrRikamA-, which no one would say is a good thing. We accept the fact that the last example is possible as a consequence of abuse of these more limited good things, and over time we learn conventions about what to do and not do. If you saw code like that playground example anywhere but a compiler test case (it's really go/test/rename.go), you'd encourage the author to write it differently.

Every language feature is overused at first. Experimenting is how we explore and chart the boundaries of what a language feature is and is not good for. The experiments that fail are the ones we later understand as overuse. When Go was first created, the pieces most unique to Go were channels and goroutines, so naturally we emphasized them in our talks about the language and our examples. This led to overuse of channels in particular, and in response we had to develop advice about when not to use channels: Don't use them for lightweight iterators. Don't use them when a plain mutex is sufficient, but do use them when you might use a mutex+condition variable in another language.

Back to the specifics of alias: as I said in September, based on our experience with Go and with very large Go code bases, Rob, Robert, Ian, and I believe that Go needs some kind of indirection mechanism, to enable incremental evolution of and changes to the layout of Go source trees (C/C++ programs use a combination of typedef and #define). Alias has been designed to (we think) harmonize with and be orthogonal to the rest of the language, in the spirit of Go's design, a generalization of typedef without the abuses of #define. The community feedback in the proposal discussion raised important, objective technical suggestions to limit abuse, and in response we introduced (we think) reasonable limits on alias: it can be used only at top level, it can refer only to imported symbols, it cannot refer to package unsafe. The feedback also included some important but more subjective arguments, such as the "detriment of the readability and comprehension" of Go programs. These latter arguments being subjective, all I can say is that we hear you but we disagree that they outweigh the benefits, keeping in mind especially that there is a natural bias to see different as unreadable, especially at first; that every language feature can be abused, so potential for abuse is not by itself disqualifying; and that every language feature is overused at first, as part of the natural process of exploring how it should be used..."

Contributor

james-lawrence commented Nov 4, 2016 edited

for whats its worth, I'm more in favor of something along the lines of #8082 approach than this. 8082 potentially solves a more general problem (one I've run into repeatedly) and helps move the language forward.

Contributor

griesemer commented Nov 4, 2016

@james-lawrence #8082 may address an overlapping issue for interfaces, but alias declarations are not just for interfaces. Many types are not interfaces.

Contributor

rsc commented Nov 4, 2016 edited

I wrote in September:

I think we should move forward with this at least to having an implementation checked in that people can try. Robert, can you please implement the current design doc (which LGTM), so we can try it out during the (current) Go 1.8 cycle?

We now have some experience with that implementation, and as a result we've identified two serious difficulties: #17746 (embedding a renaming alias) and #17784 (the effect of an alias inside a const block iota list). From #17746 it seems that the semantics need to be adjusted, perhaps to disallow renaming or perhaps to work out the implications on embedding more deliberately. From #17784 it seems that perhaps an alternate syntax, one that doesn't try to fit into the existing declaration forms, should be considered.

It is too late in the Go 1.8 cycle to consider those technical problems in the depth they deserve. In order to give us more time to address them properly, I suggest we back out the current alias implementation from Go 1.8.

This proposal was made to address a real problem that exists when updating large bodies of code. We should make another attempt at solving that problem in the next cycle, especially making sure that we have more time to try out the tentative solution during the regular development cycle. (Ideally, we'd want something ready to try when Go 1.9 opens on February 1, so that minor adjustments can be made during the cycle.)

/cc @bradfitz @griesemer @ianlancetaylor @mdempsky @robpike

Contributor

ianlancetaylor commented Nov 4, 2016

I agree that backing out and trying again for 1.9 is the right call. Especially for #17746 we were really getting into the weeds trying to define how it should work on the fly. This is the wrong point in the release cycle for that.

Member

mdempsky commented Nov 4, 2016

I also agree with deferring to 1.9. The issues we've identified aren't technically challenging to address, but I'm worried about 1) picking the right solution on a short time frame, and 2) if we run into yet more subtle issues nearer to the release date.

Contributor

robpike commented Nov 4, 2016

This is why we prototype. Lesson to learn: make significant changes earlier in the cycle so they are tested before the freeze happens.

Completely agree we need to back out and retry.

Contributor

griesemer commented Nov 4, 2016

I like to add that none of these problems were foreseen by us (unfortunately) nor pointed out in the ensuing community discussion. They only became apparent once we had an implementation available that allowed us to gather concrete experience.

Owner

bradfitz commented Nov 4, 2016

@griesemer, you should do a talk about that.

@gopherbot gopherbot closed this in 87f4e36 Nov 4, 2016

@gopherbot gopherbot pushed a commit to golang/net that referenced this issue Nov 4, 2016

@bradfitz bradfitz Revert "context: use Go 1.8 type alias for CancelFunc and Context"
This reverts commit dffc95c.

Aliases aren't going into Go 1.8. See:
golang/go#16339 (comment)

Updates #16339

Change-Id: Ie37e37349db596a89b4944179ab87b6f0577c826
Reviewed-on: https://go-review.googlesource.com/32753
Reviewed-by: Ian Lance Taylor <iant@golang.org>
Run-TryBot: Ian Lance Taylor <iant@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
158696d

CL https://golang.org/cl/32819 mentions this issue.

@gopherbot gopherbot pushed a commit that referenced this issue Nov 4, 2016

@griesemer griesemer go/types: revert user-visible changes related to aliases
Reason: Decision to back out current alias implementation.
For #16339 (comment).

Change-Id: Ie04f24e529db2d29c5dd2e36413f5f37f628df39
Reviewed-on: https://go-review.googlesource.com/32819
Reviewed-by: Matthew Dempsky <mdempsky@google.com>
039e60c

CL https://golang.org/cl/32756 mentions this issue.

CL https://golang.org/cl/32757 mentions this issue.

CL https://golang.org/cl/32823 mentions this issue.

CL https://golang.org/cl/32822 mentions this issue.

CL https://golang.org/cl/32824 mentions this issue.

CL https://golang.org/cl/32825 mentions this issue.

I am impressed by the efforts deployed by the core proponents to follow a rational and calm process, i.e., answer constructively to every point without the patronization or aggressivity that characterizes many open source projects, prioritize experiments over debates and, most importantly, adjust their their decisions when new facts arrive.
This is a true business case about scaling up an open source project in a professional & inclusive manner and shall be advertised as part of the Go mindset. Although I am still completely negative on this proposal (fully aligned with @davecheney feedback), I now trust the team that they will get something right out of it. Process and personality matters for long-term outcomes.

@gopherbot gopherbot pushed a commit that referenced this issue Nov 4, 2016

@griesemer griesemer cmd/compile: revert user-visible changes related to aliases
Reason: Decision to back out current alias implementation.

Leaving import/export related code in place for now.

For #16339.

TBR=mdempsky

Change-Id: Ib0897cab2c1c3dc8a541f2efb9893271b0b0efe2
Reviewed-on: https://go-review.googlesource.com/32757
Reviewed-by: Robert Griesemer <gri@golang.org>
8e97053

CL https://golang.org/cl/32827 mentions this issue.

@gopherbot gopherbot pushed a commit that referenced this issue Nov 5, 2016

@griesemer griesemer Revert "cmd/vet: teach vet about ast.AliasSpec"
This reverts commit aa8c8e7.

Reason: Decision to back out current alias implementation.

For #16339.

Change-Id: I4db9a8d6b3625c794be9d2f1ff0e9c047f383d28
Reviewed-on: https://go-review.googlesource.com/32827
Run-TryBot: Robert Griesemer <gri@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Chris Manghane <cmang@golang.org>
26e4377

@gopherbot gopherbot pushed a commit that referenced this issue Nov 5, 2016

@griesemer griesemer Revert "go/printer: support for printing alias declarations"
This reverts commit 59c63c7.

Reason: Decision to back out current alias implementation.

For #16339.

Change-Id: Idd135fe84b7ce4814cb3632f717736fc6985634c
Reviewed-on: https://go-review.googlesource.com/32822
Reviewed-by: Chris Manghane <cmang@golang.org>
7179c1a

@gopherbot gopherbot pushed a commit that referenced this issue Nov 5, 2016

@griesemer griesemer Revert "go/ast, go/parser: parse alias declarations"
This reverts commit 57ae833.

Reason: Decision to back out current alias implementation.

For #16339.

Change-Id: I7bcc04ac87ea3590999e58ff65a7f2e1e6c6bc77
Reviewed-on: https://go-review.googlesource.com/32823
Reviewed-by: Matthew Dempsky <mdempsky@google.com>
2808f1f

@gopherbot gopherbot pushed a commit that referenced this issue Nov 5, 2016

@griesemer griesemer Revert "go/scanner, go/token: recognize => (ALIAS) token"
This reverts commit 776a901.

Reason: Decision to back out current alias implementation.

For #16339.

Change-Id: Icb451a122c661ded05d9293356b466fa72b965f3
Reviewed-on: https://go-review.googlesource.com/32824
Reviewed-by: Matthew Dempsky <mdempsky@google.com>
a1a688f

@gopherbot gopherbot pushed a commit that referenced this issue Nov 5, 2016

@griesemer griesemer Revert "cmd/compile/internal/syntax: support for alias declarations"
This reverts commit 32db3f2.

Reason: Decision to back out current alias implementation.

For #16339.

Change-Id: Ib05e3d96041d8347e49cae292f66bec791a1fdc8
Reviewed-on: https://go-review.googlesource.com/32825
Reviewed-by: Matthew Dempsky <mdempsky@google.com>
429edcf

@gopherbot gopherbot pushed a commit to golang/tools that referenced this issue Nov 5, 2016

@griesemer griesemer go/gcimporter15: revert user-visible changes related to aliases
Reason: Decision to back out current alias implementation.
For golang/go#16339 (comment).

Change-Id: Id2a394d78a8661c767bcc05370b81f79d9bfb714
Reviewed-on: https://go-review.googlesource.com/32756
Run-TryBot: Robert Griesemer <gri@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Chris Manghane <cmang@golang.org>
Reviewed-by: Matthew Dempsky <mdempsky@google.com>
46c63f3
Contributor

rogpeppe commented Nov 7, 2016

As I said on a thread in golang-nuts, I'm in favour of type-only aliases with the "type S = T" notation, as consts can easily be done anyway, mutable global variables are a smell and that notation is by far the most obvious.

One other issue occurs to me:

There's another issue that comes to mind though - which value should be used for Type.PkgPath?

It wouldn't be surprising if someone were checking for specific type by looking at the combination of that field and the type name. For example:

package main

import (
    "reflect"
    "golang.org/x/net/context"
)

func main() {
    t := reflect.TypeOf((*context.Context)(nil)).Elem()
    if p := t.PkgPath(); p != "golang.org/x/net/context" {
        panic("unexpected " + p)
    }
}

A quick grep through the packages in my GOPATH found me a couple of places
that are doing this. I think still it would probably be OK to change this though - this
only becomes a problem if an aliased type is actually being
used in this way.

atdiar commented Nov 7, 2016 edited

@rogpeppe

There's another issue that comes to mind though - which value should be used for Type.PkgPath?

Off the top of my head, that shouldn't be an issue since this field is the empty string unless exported.
And aliases allow to transitively export and rename API elements as per the current proposal.

I'd like to emphasize, again, that I don't see the context move as the best use case to present for this proposal. The original use case @gri presented seems more befitting. The only thing is that, strictly speaking, an API is refactored; not so much the code/text. That's if we care about compatibility (backward and forward). As seen in the context case, build tags are required which has repercussions on ABI compatibility (for people who do not publish their source code, that could have implications).

trebe commented Nov 7, 2016

If type T = otherpkg.S is really to assist package refactoring, then I suggest that we add the following two requirements:

  1. T must be an exported name (i.e. upper case)
  2. T must not be referenced inside the same package. Rationale: The reason to define type T = ...; is to make existing clients continue to work during the refactoring. If we allow T to be used in the same package, then it will be used the way typedefs are used in C or just by lazy typists (type Foo = somelongpkgname.SomeStupidFooTypeName)
Member

bcmills commented Nov 7, 2016

@trebe

If we allow T to be used in the same package, then it will be used the way typedefs are used in C or just by lazy typists (type Foo = somelongpkgname.SomeStupidFooTypeName)

That kind of local aliasing isn't just for "lazy typists". If you're dealing with a generated API (e.g. a protobuf package), you can easily end up with a name like SomeStupidTypeName_SomeOneofField_SomeOtherField that you need to use frequently. Aliasing that locally to something like otherField can make the code more readable, not just reduce typing.

Merovius commented Nov 7, 2016

@trebe Refactorings are not the only use case mentioned in this proposal for aliases (which is a strength of the proposal. It means it solves several different problems). Two other use cases mentioned (so far) where a) implementing protocol buffer public imports and b) allowing drop-in augmentations of existing packages (like x/image/draw). There might be others that I'm missing right now.

Contributor

nathany commented Nov 8, 2016

@Merovius Aliases were also proposed as a way to design a new API that is split into sub-packages for organizational purposes, while the clients need not care about those sub-packages.

See #16339 (comment)

(not something I've personally run into yet)

CL https://golang.org/cl/33019 mentions this issue.

@gopherbot gopherbot pushed a commit that referenced this issue Nov 10, 2016

@griesemer griesemer go/types: remove unused alias-related testdata files
They interfere with gofmt -w across this directory.

Follow-up on https://go-review.googlesource.com/32819.

For #16339 (comment).

Change-Id: I4298b6117d89517d4fe6addce3942d950d821817
Reviewed-on: https://go-review.googlesource.com/33019
Reviewed-by: Matthew Dempsky <mdempsky@google.com>
add8028
Contributor

rogpeppe commented Nov 14, 2016

@atdiar

Off the top of my head, that shouldn't be an issue since this field is the empty string unless exported.

I was talking about the value returned by the Type.PkgPath, not the value in the StructField. This is always non-empty for named non-built-in types.

atdiar commented Nov 14, 2016 edited

@rogpeppe You're right, confusion is mine ( was thinking of Method.PkgPath)

In any case, since this is a compile time mechanism only, I think that the PkgPath should be the one corresponding to the package in which the object definition was written.

CL https://golang.org/cl/33365 mentions this issue.

@gopherbot gopherbot pushed a commit that referenced this issue Nov 18, 2016

@griesemer griesemer spec: remove => (alias) operator from Operators and Delimiters section
(Revert of https://go-review.googlesource.com/#/c/32310/)

For #16339.
Fixes #17975.

Change-Id: I36062703c423a81ea1c5b00f4429a4faf00b3782
Reviewed-on: https://go-review.googlesource.com/33365
Reviewed-by: Ian Lance Taylor <iant@golang.org>
0eb26fa
Contributor

rsc commented Nov 27, 2016

Locking because aliases are no longer proposed.

@rsc rsc locked and limited conversation to collaborators Nov 27, 2016

Contributor

rsc commented Dec 1, 2016

Although aliases were not adopted for Go 1.8, we still want to solve the underlying problem for Go 1.9. I’ve filed a new issue #18130 to start a discussion about our options. Please see that issue and the corresponding background article “Codebase Refactoring (with help from Go)” for more information. Thanks.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.