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