That is a great question! There are two parts to the answer, one current/historic, and the upcoming improvement.
Indeed, currently there is special support for creating a reference to an element of a dictionary. Like you showed in your example, if dict
is a dictionary, then let ref = &dict[key] as &T
allows taking a reference to the element. If the element exists, uses of ref
will succeed. If there is no element, i.e. the reference points to nil
, then uses of ref
will abort the program (!)
This turned out to be a bad design decision for multiple reasons:
- It only works for dictionary lookups specifically, not generally for any optional
- Uses of such a reference may be statically valid (i.e. they “type check”, there are no errors), but then fail at run-time
This is why FLIP 722: Optional References to Indexed Accesses improves the behavior:
Now, when taking a reference to an optional, e.g. when indexing into a dictionary, or an optional field, the results is an optional reference. This change is already implemented in Cadence, but has not been released/deployed yet.
Independent of the current or future behavior, I would recommend handling the case where the field is nil, e.g. by returning an optional reference (&Thing?
).
In an upcoming release the function thus would simply be:
fun borrowThing(): &Thing? {
return &self.thing as &Thing?
}
If you want to keep failing the program when there is no thing / it is nil
, you could force-unwrap or panic (which I don’t recommend, but up to you), e.g.
fun borrowThing(): &Thing {
let ref = &self.thing as &Thing?
return ref!
}
If you need to implement the function for current versions of Cadence (v0.21, v0.23), then there is still a way but it is more involved: You first have to move the thing out of the field, then take the reference to the non-optional thing, move the thing back to the field, and finally you can return the reference, like so:
fun borrowThing(): &Thing? {
// Try to move the thing out of the field, and if there is any,
// set the field to nil and the variable `thing` to the thing
if let thing <- self.thing <- nil {
// `thing` has type `@Thing`,
// i.e. it is non-optional, so we can take a reference to it
let ref = &thing as? &Thing
// Move the thing back to the field.
// We know the field is empty (nil), so we can use force-move.
self.thing <-! thing
// Finally, return the ref, wrapped in an optional
return ref
}
// There was no thing in the field, return nil
return nil
}
Or again, if you do not want to return an optional you can also just panic, e.g:
fun borrowThing(): &Thing {
// Try to move the thing out of the field, and if there is any,
// set the field to nil and the variable `thing` to the thing
if let thing <- self.thing <- nil {
// `thing` has type `@Thing`,
// i.e. it is non-optional, so we can take a reference to it
let ref = &thing as? &Thing
// Move the thing back to the field.
// We know the field is empty (nil), so we can use force-move.
self.thing <-! thing
// Finally, return the ref
return ref
}
// There was no thing in the field
panic("no thing!")
}