Skip to content

Proposal: Alias declarations for Go #16339

Closed

Description

@griesemer

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

Activity

  1. added this to the Proposal milestone on Jul 12, 2016
  2. self-assigned this
    on Jul 12, 2016
  3. griesemer commented on Jul 12, 2016

    @griesemer
    ContributorAuthor
  4. nicerobot commented on Jul 12, 2016

    @nicerobot

    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.

  5. gopherbot commented on Jul 13, 2016

    @gopherbot
    Contributor

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

  6. Merovius commented on Jul 13, 2016

    @Merovius
    Contributor

    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.

  7. matttproud commented on Jul 13, 2016

    @matttproud
    Contributor

    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)

  8. cespare commented on Jul 13, 2016

    @cespare
    Contributor

    @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 =.

  9. josharian commented on Jul 13, 2016

    @josharian
    Contributor

    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.
  10. dlsniper commented on Jul 13, 2016

    @dlsniper
    Contributor

    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(?)

  11. Kunde21 commented on Jul 13, 2016

    @Kunde21

    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 ..
  12. jimmyfrasche commented on Jul 13, 2016

    @jimmyfrasche
    Member

    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.

  13. mem commented on Jul 13, 2016

    @mem

    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.

  14. josharian commented on Jul 13, 2016

    @josharian
    Contributor

    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() { /* ... */ }
  15. 373 remaining items

  16. atdiar commented on Nov 7, 2016

    @atdiar

    @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).

  17. trebe commented on Nov 7, 2016

    @trebe

    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)
  18. bcmills commented on Nov 7, 2016

    @bcmills
    Contributor

    @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.

  19. Merovius commented on Nov 7, 2016

    @Merovius
    Contributor

    @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.

  20. nathany commented on Nov 8, 2016

    @nathany
    Contributor

    @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)

  21. gopherbot commented on Nov 10, 2016

    @gopherbot
    Contributor

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

  22. rogpeppe commented on Nov 14, 2016

    @rogpeppe
    Contributor

    @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.

  23. atdiar commented on Nov 14, 2016

    @atdiar

    @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.

  24. gopherbot commented on Nov 18, 2016

    @gopherbot
    Contributor

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

  25. rsc commented on Nov 27, 2016

    @rsc
    Contributor

    Locking because aliases are no longer proposed.

  26. locked and limited conversation to collaborators on Nov 27, 2016
  27. rsc commented on Dec 1, 2016

    @rsc
    Contributor

    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 join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions