- Proposal: SE-NNNN
- Authors: Daryle Walker, Author 2
- Review Manager: TBD
- Status: Awaiting review
During the review process, add the following fields as needed:
- Decision Notes: Rationale, Additional Commentary
- Bugs: SR-NNNN, SR-MMMM
- Previous Revision: 1
- Previous Proposal: SE-XXXX
This is a proposal to define aliases to objects.
Aliasing allows a named object to actually refer to another object instead of newly-allocated storage. Referring to an object with a simple name isn't very useful, but referring to an object needing a complex expression to point to it can help with reducing typing.
However, aliasing has a cost. Compilers have to make certain assumptions if objects can have multiple names referring to them, and these assumptions reduce what kinds of optimizations can be made.
Language design can make a difference in how code can be optimized. Languages like C and C++ assume aliasing is allowed by default, limiting how many optimizations can be done. More recent versions of C have a keyword ("restrict
") to ban certain objects from aliasing. Other languages go the other way; you need to take extra measures to alias objects, since object handling bars aliasing by default.
Swift is currently an alias-adverse language. The model for the equivalent of pointers is supposed to be for short-term use, and not persisted. Other constructs that would use references: read-write properties, read-write subscripts, and inout
function parameters, can all be implemented by copy-in-then-copy-out, presumably to avoid alias dynamics and its anti-optimizations. So the scope of aliases here will be limited to local-scale renaming of object locations that the compiler can connect statically.
Yes, the use case is currently weak, but it is a stepping stone for stronger cases, like changing the interface of an object with (currently not in the language) strong type-aliases without copies.
The solution is to introduce a new kind of object declaration. It uses a new keyword pose
in the same place as let
or var
. It must be initialized with an expression that specifies an object, and be typed with a layout-compatible type (like the unsafeBitCast
function).
struct Sample {
var test1 = (1, 2, 3, "apple")
//...
func trial1() {
pose firstTestNumber = test1.0
print(firstTestNumber) // prints "1"
//...
firstTestNumber = 4
print(test1.0) // prints "4"
}
}
When an object is used, the compiler associates the object with some sort of location ID. An alias just reuses its original's ID instead of having one of its own.
Here, the substitution is simple, but longer chains are imaginable. With a local-scope limitation, aliases work kind-of like macro constants in C.
Add to the "Grammar of a Declaration":
declaration → alias-declaration
Add a new section "Grammar of an Alias Declaration":
alias-declaration → attributes_opt declaration-modifiers_opt pose pattern-initializer-list
An alias declaration can only be in the local scope of a function. The pattern initializer list must have expressions that refer to one of these kinds of source objects:
- a named object, including function parameters
- a member of a qualifying tuple object
- a stored property of a qualifying
struct
(orclass
?) object
A source object must have a lifetime at least as long as any aliases to it. A source object cannot have willSet
and/or didSet
observers. The alias poses as an object of its type annotation, defaulting to the source object's type if omitted. An annotation must be of the source object's type or a layout-compatible type. An alias has the same mutability status as its source object.
An alias has the same operations as its annotated type, using the storage of the source object. An alias used as an inout
function argument is banned if it and at least one other inout
argument share memory (in whole or in part).
Since source objects are restricted to have their storage established statically, the compiler can reuse a source object's location ID when an alias to that source is referenced. Since an alias doesn't escape its containing function (any returns or inout
action would copy to/from the source object), additional global aliasing checks are avoided.
Besides the new keyword pose
, which should be conditional if possible, the changes are additive. I don't think it is legal to currently use an identifier pose
in its planned syntax, so there should be no code to migrate.
The effects of aliases happen only during translation, reusing locations of either named objects or sub-objects of named objects. Since they shouldn't escape the function containing them (and can't be sub-objects of another type), the ABI should be unaffected.
Since aliases shouldn't leak out from being a function implementation aid, there should be no effect on the API.
An alternative is to do nothing. This would currently make reuse of a sub-object for read-write a bit more wordy. But this facility may be more useful if making interface-wise different but layout-compatible types (like strong type-aliases) is added.
Another alternative is the concept of lenses. It's a functional programming concept, but could be seen as a generalization of C++'s pointer-to-member concept. It not only covers getting a direct member, but indirect members too (in C++ parlance, that would be a chain of pointers-to-member specifying a nested sub-object). These lenses are first-class objects themselves while aliases are symbolic substitution; an alias can be replaced by copying-and-pasting the expression for the source object (except an alias would calculate the object's location just once).