References
It is possible to create references to objects, i.e. resources or structures. A reference can be used to access fields and call functions on the referenced object.
References are copied, i.e. they are value types.
References have the type &T
, where T
is the type of the referenced object.
References are created using the &
operator.
The reference type must be explicitly provided,
for example through a type annotation on a variable declaration,
or a type assertion using the as
operator.
The reference type must be a supertype of the referenced object's type.
When creating a reference to an optional value, the result is an optional reference. If the referenced value is nil, the resulting reference itself will be nil. If the referenced value exists, then forcing the optional reference will yield a reference to that value:
References are covariant in their base types.
For example, &T
is a subtype of &U
, if T
is a subtype of U
.
References can be freely upcasted and downcasted, and are covariant in their referenced type.
So, for example, for some struct S
, &S
is a subtype of &AnyStruct
, but not of &Int
.
References are ephemeral, i.e. they cannot be stored. Instead, consider storing a capability and borrowing it when needed.
Authorized References
By default, references are unauthorized. However, they may also be authorized to a set of entitlements
Authorized references have the auth
modifier,
along with the set of entitlements to which they are authorized. The full syntax is:
auth(E, F, G) &T
for a reference authorized to E
, F
and G
,
or auth(E | F | G) &T
for a refernece authorized to E
, F
, or G
.
Authorized references are subtypes of unauthorized references.
Entitlements can only be given to references when they are created, and references to a value can only be created by the owner of the value. When creating a reference, that reference can be given any set of entitlements the value owner wishes to add.
Possessing an entitlement allows a reference to have access to functions and fields on its referenced type
that require that entitlement. E.g, if we extended the HasCount
interface with a function:
Then an unauthorized reference of type &{HasCount}
would be unable to call resetCount
.
However, we can create a reference that can, like so:
It is important to note that while references are covariant (and downcastable) with respect to their reference type, the authorization portion of the reference can never be downcast. In fact, the only way to "add" entitlements to a reference is to do so at the time of its creation, like in the example above. A reference will never have any more entitlements than the set with which it was created, and the set of entitlements on a reference at runtime will always match the set expressed in its static type. One implication of this is that upcasting an authorized reference actually changes its runtime type:
The benefit of this is that there is never any "surprising" behavior with regards to entitlements, every reference value is transparent about what it is capable of at runtime.
While entitlement sets on references cannot be downcast, they can be upcast, or used in places expecting supertypes,
and have special subtyping rules based on whether they are |
or ,
-separated sets.
In general, an entitlement set {Us}
is a subtype of an entitlement set {Vs}
when {Us}
has more entitlements
in it than {Vs}
, and when both are ,
-separated (as they will be in most cases), this is the rule exactly:
{Us}
is a subset of {Vs}
when it is a superset of {Vs}
.
Conversely, if both are |
-separated, the rule is reversed:
{Us}
is a subset of {Vs}
when it is a subset of {Vs}
.
It may be helpful to think of this as saying that {Us}
is more specific than {Vs}
in this case;
{Vs}
expresses a set of entitlements that the reference might possess,
while {Us}
is expressing a more specific set of potential entitlements.
Lastly, if {Us}
is ,
-separated while {Vs}
is |
-separated,
then {Us}
is a subset of {Vs}
when any of the Us
also appears in {Vs}
.
To see why, consider again that {Vs}
expresses a set of entitlements that the reference might possess,
and as long as at least one of these entitlements is in {Us}
(which is a set of entitlements that we know the reference has),
then the description provided by {Vs}
is correct.
As an example to illustrate these rules:
References and Entitlement Mappings
In most situations, an entitlement mapping is valid in the auth
portion of a reference type.
However, in certain specific circumstances in the definition of a field or function on a composite type, an entitlement mapping may be used in an auth
modifier.
When a field is defined with an entitlement mapping:
Here, the M
in auth(M) &T
indicates that the entitlements that the reference produced by an iRef.foo
access will have
are determined by the entitlements to I
that iRef
has, for some iRef
value that is a reference to {I}
. Conceptually,
it creates a correspondence between the "output" reference's type and the "input" access modifier.
When an accessor function is defined with an entitlement mapping:
The M
in the auth(M) &T
of the function's return type annotation indicates the same thing as in the field case.
However, in this example M
is also used in a reference type within the body of the function.
Inside the body of function with entitlement-mapped access,
the name of the entitlement mapping may be used as a stand-in for the output entitlements of the map.
Field and Index Access
References to container types (structs/resources, dictionaries and arrays) can be used to access (read/write) fields or elements of the container.
When a field/index is read through a reference, it will return:
- A reference, if the field / value at index is also container-typed.
- Or, the concrete value, if the value is a primitive type.
For example, consider the below Collection
resource which has two fields: one (id) is String-typed,
and the other (ownedNFTs) is dictionary-typed.
Thus,
Similarly, accessing an element of an array/dictionary will return a reference.
It is also important to note that, in the above examples, the returned references have no entitlements. i.e: they are non-auth references.
To get entitled references for struct/resource fields, they must be defined with entitlement mappings. However, accessing a value at an index/key of an array/dictionary reference would always return a non-auth reference.
Index Assignment
Assigning to an index of an array or a dictionary reference is an entitled-operation.
In other words, the assignment operator for arrays/dictionaries would also have the Mutate
and Insert
built-in entitlements.
Think of assignment as a built-in function with Mutate
or (Insert, Remove)
entitlements. e.g:
Note that the syntax for having nested entitlements in access modifiers like (Mutate | (Insert, Remove))
is not currently supported, but this is for illustration purpose only.
Thus,
Reference Validity
Ephemeral references stay valid throughout the course of the program. However, references to resources can become invalid during the execution of a program, if the referenced resource is moved or destroyed after taking the reference.
A reference is invalidated upon the first transfer of the underlying resource, regardless of the origin and the destination.
Invalidations of storage references are not statically caught, but only at run-time.