blob: b57f75b1eaa1454302274a65a0d93c0d971e7027 [file] [log] [blame] [view]
Robert Griesemer14874462016-07-12 17:25:40 -06001# Proposal: Alias declarations for Go
2
3Authors: Robert Griesemer & Rob Pike.
Robert Griesemer51d585a2016-07-18 15:03:30 -07004Last updated: July 18, 2016
Robert Griesemer14874462016-07-12 17:25:40 -06005
6Discussion at https://golang.org/issue/16339.
7
8## Abstract
9We propose to add alias declarations to the Go language. An alias declaration
10introduces an alternative name for an object (type, function, etc.) declared
Robert Griesemer51d585a2016-07-18 15:03:30 -070011elsewhere. Alias declarations simplify splitting up packages because clients
12can be updated incrementally, which is crucial for large-scale refactoring.
13They also facilitate multi-package "components" where a top-level package
14is used to provide a component's public API with aliases referring to the
15componenent's internal packages. Alias declarations are
16important for the Go implementation of the "import public" feature of Google
17protocol buffers. They also provide a more fine-grained and explicit
18alternative to "dot-imports".
Robert Griesemer14874462016-07-12 17:25:40 -060019
20## 1. Motivation
21Suppose we have a library package L and a client package C that depends on L.
22During refactoring of code, some functionality of L is moved into a new
23package L1, which in turn may require updates to C. If there are multiple
24clients C1, C2, ..., many of these clients may need to be updated
25simultaneously for the system to build. Failing to do so will lead to build
26breakages in a continuous build environment.
27
28This is a real issue in large-scale systems such as we find at Google because
29the number of dependencies can go into the hundreds if not thousands. Client
30packages may be under control of different teams and evolve at different
31speeds. Updating a large number of client packages simultaneously may be close
32to impossible. This is an effective barrier to system evolution and maintenance.
33
34If client packages can be updated incrementally, one package (or a small batch
35of packages) at a time, the problem is avoided. For instance, after moving
36functionality from L into L1, if it is possible for clients to continue to
37refer to L in order to get the features in L1, clients don’t need to be
38updated at once.
39
40Go packages export constants, types (incl. associated methods), variables, and
41functions. If a constant X is moved from a package L to L1, L may trivially
42depend on L1 and re-export X with the same value as in L1.
43
44```
45package L
46import "L1"
47const X = L1.X // X is effectively an alias for L1.X
48```
49
50Client packages may use L1.X or continue to refer to L.X and still build
51without issues. A similar work-around exists for functions: Package L may
52provide wrapper functions that simply invoke the corresponding functions
53in L1. Alternatively, L may define variables of function type which are
54initialized to the functions which moved from L to L1:
55
56```
57package L
58import "L1"
59var F = L1.F // F is a function variable referring to L1.F
60func G(args…) Result { return L1.G(args…) }
61```
62
63It gets more complicated for variables: An incremental approach still exists
64but it requires multiple steps. Let’s assume we want to move a variable V
65from L to L1. In a first step, we declare a pointer variable Vptr in L1
66pointing to L.V:
67
68```
69package L1
70import "L"
71var Vptr = &L.V
72```
73
74Now we can incrementally update clients referring to L.V such that they use
75(\*L1.Vptr) instead. This will give them full access to the same variable.
76Once all references to L.V have been changed, L.V can move to L1; this step
77doesn’t require any changes to clients of L1 (though it may require additional
78internal changes in L and L1):
79
80```
81package L1
82import "L"
83var Vptr = &V
84var V T = ...
85```
86
87Finally, clients may be incrementally updated again to use L1.V directly after
88which we can get rid of Vptr.
89
90There is no work-around for types, nor is possible to define a named type T in
91L1 and re-export it in L and have L.T mean the exact same type as L1.T.
92
93Discussion: The multi-step approach to factor out exported variables requires
94careful planning. For instance, if we want to move both a function F and a
95variable V from L to L1, we cannot do so at the same time: The forwarder F
96left in L requires L to import L1, and the pointer variable Vptr introduced
97in L1 requires L1 to import L. The consequence would be a forbidden import
98cycle. Furthermore, if a moved function F requires access to a yet unmoved V,
99it would also cause a cyclic import. Thus, variables will have to be moved
100first in such a scenario, requiring multiple steps to enable incremental
101client updates, followed by another round of incremental updates to move
102everything else.
103
104## 2. Alias declarations
105To address these issues with a single, unified mechanism, we propose a new
106form of declaration in Go, called an alias declaration. As the name suggests,
107an alias declaration introduces an alternative name for a given object that
Robert Griesemer51d585a2016-07-18 15:03:30 -0700108has been declared elsewhere, in a different package.
Robert Griesemer14874462016-07-12 17:25:40 -0600109
110An alias declaration in package L makes it possible to move the original
111declaration of an object X (a constant, type, variable, or function) from
112package L to L1, while continuing to define and export the name X in L.
113Both L.X and L1.X denote the exact same object (L1.X).
114
115Note that the two predeclared types byte and rune are aliases for the
116predeclared types uint8 and int32. Alias declarations will enable users
Robert Griesemer51d585a2016-07-18 15:03:30 -0700117to define their own aliases, similar to byte and rune.
Robert Griesemer14874462016-07-12 17:25:40 -0600118
119## 3. Notation
120The existing declaration syntax for constants effectively permits
121constant aliases:
122
123```
124const C = L1.C // C is effectively an alias for L1.C
125```
126
127Ideally we would like to extend this syntax to other declarations
128and give it alias semantics:
129
130```
131type T = L1.T // T is an alias for L1.T
132func F = L1.F // F is an alias for L1.F
133```
134
135Unfortunately, this notation breaks down for variables, because it already
136has a given (and different) meaning in variable declarations:
137
138```
139var V = L1.V // V is initialized to L1.V
140```
141
Robert Griesemer51d585a2016-07-18 15:03:30 -0700142Instead of "=" we propose the new alias operator "=>" to solve the
Robert Griesemer14874462016-07-12 17:25:40 -0600143syntactic issue:
144
145```
Robert Griesemer51d585a2016-07-18 15:03:30 -0700146const C => L1.C // for regularity only, same effect as const C = L1.C
147type T => L1.T // T is an alias for type L1.T
148var V => L1.V // V is an alias for variable L1.V
149func F => L1.F // F is an alias for function L1.F
Robert Griesemer14874462016-07-12 17:25:40 -0600150```
151
152With that, a general alias specification is of the form:
153
Robert Griesemer3a91c2f2016-07-18 17:14:30 -0700154```
Robert Griesemer51d585a2016-07-18 15:03:30 -0700155AliasSpec = identifier "=>" PackageName "." identifier .
Robert Griesemer3a91c2f2016-07-18 17:14:30 -0700156```
Robert Griesemer14874462016-07-12 17:25:40 -0600157
Robert Griesemer51d585a2016-07-18 15:03:30 -0700158Per the discussion at https://golang.org/issue/16339, and based on feedback
159from adonovan@golang, to avoid abuse, alias declarations may refer to imported
160and package-qualified objects only (no aliases to local objects or
161"dot-imports").
162Furthermore, they are only permitted at the top (package) level,
163not inside a function.
Robert Griesemer14874462016-07-12 17:25:40 -0600164
Robert Griesemer51d585a2016-07-18 15:03:30 -0700165These restriction do not hamper the utility of aliases for the intended
166use cases. Both restrictions can be trivially lifted later if so desired;
167we start with them out of an abundance of caution.
Robert Griesemer14874462016-07-12 17:25:40 -0600168
Robert Griesemer51d585a2016-07-18 15:03:30 -0700169An alias declaration may refer to another alias.
170
171The LHS identifier (C, T, V, and F in the examples above) in an alias
172declaration is called the _alias name_ (or _alias_ for short). For each alias
173name there is an _original name_ (or _original_ for short), which is the
174non-alias name declared for a given object (e.g., L1.T in the example above).
Robert Griesemer14874462016-07-12 17:25:40 -0600175
176Some more examples:
177
178```
Robert Griesemer14874462016-07-12 17:25:40 -0600179import "oldp"
180
Robert Griesemer51d585a2016-07-18 15:03:30 -0700181var v => oldp.V // local alias, not exported
Robert Griesemer14874462016-07-12 17:25:40 -0600182
Robert Griesemer51d585a2016-07-18 15:03:30 -0700183// alias declarations may be grouped
Robert Griesemer14874462016-07-12 17:25:40 -0600184type (
Robert Griesemer51d585a2016-07-18 15:03:30 -0700185 T1 => oldp.T1 // original for T1 is oldp.T1
186 T2 => oldp.T2 // original for T2 is oldp.T2
187 T3 [8]byte // regular declaration may be grouped with aliases
Robert Griesemer14874462016-07-12 17:25:40 -0600188)
189
Robert Griesemer51d585a2016-07-18 15:03:30 -0700190var V2 T2 // same effect as: var V2 oldp.T2
Robert Griesemer14874462016-07-12 17:25:40 -0600191
Robert Griesemer51d585a2016-07-18 15:03:30 -0700192func myF => oldp.F // local alias, not exported
193func G => oldp.G
194
195type T => oldp.MuchTooLongATypeName
Robert Griesemer14874462016-07-12 17:25:40 -0600196
197func f() {
Robert Griesemer51d585a2016-07-18 15:03:30 -0700198 x := T{} // same effect as: x := oldp.MuchTooLongATypeName{}
Robert Griesemer14874462016-07-12 17:25:40 -0600199 ...
200}
201```
202
203The respective syntactic changes in the language spec are small and
204concentrated. Each declaration specification (ConstSpec, TypeSpec, etc.)
205gets a new alternative which is an alias specification (AliasSpec).
206Grouping is possible as before, except for functions (as before).
207See Appendix A1 for details.
208
209The short variable declaration form (using ":=") cannot be used to
210declare an alias.
211
Robert Griesemer51d585a2016-07-18 15:03:30 -0700212Discussion: Introducing a new operator ("=>") has the advantage of not
Robert Griesemer14874462016-07-12 17:25:40 -0600213needing to introduce a new keyword (such as "alias"), which we can't really
214do without violating the Go 1 promise (though r@golang and rsc@golang observe
215that it would be possible to recognize "alias" as a keyword at the package-
216level only, when in const/type/var/func position, and as an identifier
Robert Griesemer51d585a2016-07-18 15:03:30 -0700217otherwise, and probably not break existing code).
218
219The token sequence "=" ">" (or "==" ">") is not a valid sequence in a Go
220program since ">" is a binary operator that must be surrounded by operands,
221and the left operand cannot end in "=" or "==". Thus, it is safe to introduce
222"=>" as a new token sequence without invalidating existing programs.
223
224As proposed, an alias declaration must specify what kind of object the alias
225refers to (const, type, var, or func). We believe this is an advantage:
226It makes it clear to a user what the alias denotes (as with existing
227declarations). It also makes it possible to report an error at the location
228of the alias declaration if the aliased object changes (e.g., from being a
229constant to a variable) rather than only at where the alias is used.
Robert Griesemer14874462016-07-12 17:25:40 -0600230
231On the other hand, mdempsky@golang points out that using a keyword would
232permit making changes in a package L1, say change a function F into a type F,
233and not require a respective update of any alias declarations referring to
234L1.F, which in turn might simplify refactoring. Specifically, one could
235generalize import declarations so that they can be used to import and rename
236specific objects. For instance:
237
238```
239 import Printf = fmt.Printf
240```
241
242or
243
244```
245 import Printf fmt.Printf
246```
247
248One might even permit the form
249
250```
251 import context.Context
252```
253
254as a shorthand for
255
256```
257 import Context context.Context
258```
259
260analogously to the renaming feature available to imports already. One of the
261issues to consider here is that imported packages end up in the file scope and
262are only visible in one file. Furthermore, currently they cannot be
263re-exported. It is crucial for aliases to be re-exportable. Thus alias imports
264would need to end up in package scope. (It would be odd if they ended up in
265file scope: the same alias may have to be imported in multiple files of the
266same package, possibly with different names.)
267
Robert Griesemer51d585a2016-07-18 15:03:30 -0700268The choice of token ("=>") is somewhat arbitrary, but both "A => B" and
269"A -> B" conjure up the image of a reference or forwarding from A to B.
270The token "->" is also used in Unix directory listings for symbolic links,
271where the lhs is another name (an alias) for the file mentioned on the RHS.
Robert Griesemer14874462016-07-12 17:25:40 -0600272
273dneil@golang and r@golang observe that if "->" is written "in reverse" by
274mistake, a declaration "var X -> p.X" meant to be an alias declaration is
275close to a regular variable declaration "var X <-p.X" (with a missing "=");
276though it wouldn’t compile.
277
Robert Griesemer51d585a2016-07-18 15:03:30 -0700278Many people expressed a preference for "=>" over "->" on the tracking issue.
279The argument is that "->" is more easily confused with a channel operation.
280A few people would like to use "@" (as in _@lias_). For now we proceed with
281"=>" - the token is trivially changed down the road if there is strong general
282sentiment or a convincing argument for any other notation.
Robert Griesemer14874462016-07-12 17:25:40 -0600283
284## 3. Semantics and rules
285An alias declaration declares an alternative name, the alias, for a constant,
Robert Griesemer51d585a2016-07-18 15:03:30 -0700286type, variable, or function, referred to by the RHS of the alias declaration.
287The RHS must be a package-qualified identifier; it may itself be an alias, or
288it may be the original name for the aliased object.
289
290Alias cycles are impossible by construction since aliases must refer to fully
291package-qualified (imported) objects and package import cycles are not
292permitted.
Robert Griesemer14874462016-07-12 17:25:40 -0600293
294An alias denotes the aliased object, and the effect of using an alias is
Robert Griesemer51d585a2016-07-18 15:03:30 -0700295indistinguishable from the effect of using the original; the only visible
296difference is the name.
297
298An alias declaration may only appear at the top- (package-) level where it
299is valid to have a keyword-based constant, type, variable, or function
300declaration. Alias declarations may be grouped.
Robert Griesemer14874462016-07-12 17:25:40 -0600301
302The same scope and export rules (capitalization for export) apply as for all
Robert Griesemer51d585a2016-07-18 15:03:30 -0700303other identifiers at the top-level.
Robert Griesemer14874462016-07-12 17:25:40 -0600304
Robert Griesemer51d585a2016-07-18 15:03:30 -0700305The scope of an alias identifier at the top-level is the package block
306(as is the case for an identifier denoting a constant, type, variable,
307or function).
Robert Griesemer14874462016-07-12 17:25:40 -0600308
Robert Griesemer51d585a2016-07-18 15:03:30 -0700309An alias declaration may refer to unsafe.Pointer, but not to any of the unsafe
310functions.
Robert Griesemer14874462016-07-12 17:25:40 -0600311
Robert Griesemer51d585a2016-07-18 15:03:30 -0700312A package is considered "used" if any imported object of a package is used.
313Consequently, declaring an alias referring to an object of an package marks
314the package as used.
Robert Griesemer14874462016-07-12 17:25:40 -0600315
Robert Griesemer51d585a2016-07-18 15:03:30 -0700316Discussion: The original proposal permitted aliases to any (even local)
317objects and also to predeclared types in the Universe scope. Furthermore,
318it permitted alias declarations inside functions. See the tracking issue
319and earlier versions of this document for a more detailed discussion.
Robert Griesemer14874462016-07-12 17:25:40 -0600320
321## 4. Impact on other libraries and tools
322Alias declarations are a source-level and compile-time feature, with no
323observable impact at run time. Thus, libraries and tools operating at the
324source level or involved in type checking and compilation are expected to
325need adjustments.
326
327reflect package
328The reflect package permits access to values and their types at run-time.
329There’s no mechanism to make a new reflect.Value from a type name, only from
330a reflect.Type. The predeclared aliases byte and rune are mapped to uint8 and
331int32 already, and we would expect the same to be true for general aliases.
332For instance:
333
334```
335fmt.Printf("%T", rune(0))
336```
337
338prints the original type name int32, not rune. Thus, we expect no API or
339semantic changes to package reflect.
340
341go/\* std lib packages
342The packages under the go/\* std library tree which deal with source code will
343need to be adjusted. Specifically, the packages go/token, go/scanner, go/ast,
344go/parser, go/doc, and go/printer will need the necessary API extensions and
345changes to cope with the new syntax. These changes should be straightforward.
346
347Package go/types will need to understand how to type-check alias declarations.
348It may also require an extension to its API (to be explored).
349
350We don’t expect any changes to the go/build package.
351
352go doc
353The go doc implementation will need to be adjusted: It relies on package go/doc
354which now exposes alias declarations. Thus, godoc needs to have a meaningful
355way to show those as well. This may be a simple extension of the existing
356machinery to include alias declarations.
357
358Other tools operating on source code
359A variety of other tools operate or inspect source code such as go vet,
360go lint, goimport, and others. What adjustments need to be made needs to be
361decided on a case-by-case basis.
362
363## 5. Implementation
364There are many open questions that need to be answered by an implementation.
365To mention a few of them:
366
367Are aliases represented somehow as “first-class” citizens in a compiler and
368go/types, or are they immediately “resolved” internally to the original names?
369For go/types specifically, adonovan@golang points out that a first-class
370representation may have an impact on the go/types API and potentially affect
371many tools. For instance, type switches assuming only the kinds of objects now
372in existence in go/types would need to be extended to handle aliases, should
373they show up in the public API. The go/types’ Info.Uses map, which currently
374mapes identifiers to objects, will require especial attention: Should it record
375the alias to object references, or only the original names?
376
377At first glance, since an alias is simply another name for an object, it would
378seem that an implementation should resolve them immediately, making aliases
379virtually invisible to the API (we may keep track of them internally only for
380better error messages). On the other hand, they need to be exported and might
381need to show up in go/types’ Info.Uses map (or some additional variant thereof)
382so that tools such as guru have access to the alias names.
383
384To be prototyped.
385
Robert Griesemer51d585a2016-07-18 15:03:30 -0700386## 6. Other use cases
387Alias declarations facilitate the construction of larger-scale libraries or
388"components". For organizational and size reasons it often makes sense to split
389up a large library into several sub-packages. The exported API of a sub-package
390is driven by internal requirements of the component and may be only remotely
391related to its public API. Alias declarations make it possible to "pull out"
392the relevant declarations from the various sub-packages and collect them in
393a single top-level package that represents the component's API.
394The other packages can be organized in an "internal" sub-directory,
395which makes them virtually inaccessible through the `go build` command (they
396cannot be imported).
397
398TODO(gri): Expand on use of alias declarations for protocol buffer's
399"import public" feature.
400
401TODO(gri): Expand on use of alias declarations instead of "dot-imports".
402
Robert Griesemer14874462016-07-12 17:25:40 -0600403# Appendix
404
405## A1. Syntax changes
406The syntax changes necessary to accommodate alias declarations are limited
407and concentrated. There is a new declaration specification called AliasSpec:
408
409```
Robert Griesemer51d585a2016-07-18 15:03:30 -0700410AliasSpec = identifier "=>" PackageName "." identifier .
Robert Griesemer14874462016-07-12 17:25:40 -0600411```
412
413An AliasSpec binds an identifier, the alias name, to the object (constant,
414type, variable, or function) the alias refers to. The object must be specified
415via a (possibly qualified) identifier. The aliased object must be a constant,
416type, variable, or function, depending on whether the AliasSpec is within a
417constant, type, variable, of function declaration.
418
419Alias specifications may be used with any of the existing constant, type,
420variable, or function declarations. The respective syntax productions are
421extended as follows, with the extensions marked in red:
422
423```
424ConstDecl = "const" ( ConstSpec | "(" { ConstSpec ";" } ")" ) .
425ConstSpec = IdentifierList [ [ Type ] "=" ExprList ] | AliasSpec .
426
427TypeDecl = "type" ( TypeSpec | "(" { TypeSpec ";" } ")" ) .
428TypeSpec = identifier Type | AliasSpec .
429
430VarDecl = "var" ( VarSpec | "(" { VarSpec ";" } ")" ) .
431VarSpec = IdentList ( Type [ "=" ExprList ] | "=" ExprList ) |
432 AliasSpec .
433
434FuncDecl = "func" FunctionName ( Function | Signature ) |
435 "func" AliasSpec .
436```
437
438## A2. Alternatives to this proposal
439For completeness, we mention several alternatives.
440
4411) Do nothing (wait for Go 2). The easiest solution, but it does not address
442the problem.
443
4442) Permit alias declarations for types only, use the existing work-arounds
445otherwise. This would be a “minimal” solution for the problem. It would
446require the use of work-arounds for all other objects (constants, variables,
447and functions). Except for variables, those work-arounds would not be too
448onerous. Finally, this would not require the introduction of a new operator
449since "=" could be used.
450
4513) Permit re-export of imports, or generalize imports. One might come up with
452a notation to re-export all objects of an imported package wholesale,
453accessible under the importing package name. Such a mechanism would address
454the incremental refactoring problem and also permit the easy construction of
455some sort of “super-package” (or component), the API of which would be the sum
456of all the re-exported package APIs. This would be an “all-or-nothing” approach
457that would not permit control over which objects are re-exported or under what
458name. Alternatively, a generalized import scheme (discussed earlier in this
459document) may provide a more fine-grained solution.