References to Nested Optional Resources

Nested optional resources are really tricky to get references to. For example:

pub resource Wrapper {
  pub var thing: @Thing?

  pub fun borrowThing(): &Thing {
    ...
  }
}

...

For some reason, Cadence doesn’t like you getting a reference to an optional nested resources. The weird part is that, in a normal NFT contract, &self.ownedNFTs[id] as &NFT isn’t a problem even though self.ownedNFTs[id] is in fact itself an optional type. Does anyone have any ideas of how to get a reference to a nested optional resource?

In terms of calling a function on the @Thing? resource, I know you can just do self.thing?.method, but getting a reference directly is more interesting to me.

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!")
}