Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Swift proposal for object aliases

Local Object Aliases

During the review process, add the following fields as needed:

Introduction

This is a proposal to define aliases to objects.

Swift-evolution thread: 1, 2

Motivation

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.

Proposed solution

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.

Detailed design

Add to the "Grammar of a Declaration":

declarationalias-declaration

Add a new section "Grammar of an Alias Declaration":

alias-declarationattributes_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 (or class?) 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.

Source compatibility

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.

Effect on ABI stability

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.

Effect on API resilience

Since aliases shouldn't leak out from being a function implementation aid, there should be no effect on the API.

Alternatives considered

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.