Default value for ContractId

I’d need (I believe) to use ‘fromOptional’ called on a ContractId.
The syntax is clear.

When I try to write
myCid <- fromOptional 0 myCid

I get a parse error:
Couldn't match expected type ‘Update (ContractId (Optional a3))’ with actual type ‘Int’

Instructions preceding the one with the error:

myCid <- lookupByKey @myTemplate (key1, key2)
assertMsg "myCid not found" (isSome myCid)

What can be a default value for a ContractId? Or, is here another problem?
Thank you for any hints.

1 Like

Hi @mlyczak, ContractIds are similar to pointers in other languages but they are abstract (so you cannot convert a number to a contract id) and there are no null pointers. That means that there isn’t really any contract id that you can default to. However, looking at your code, it seems like that’s not actually what you want. You have an assertion that the contract id is present and if it isn’t, you crash. So all that’s needed is a bit of pattern matching to get the value out of the Optional (ContractId a) in the case where it is Some:

myCidOpt <- lookupByKey @myTemplate (key1, key2)
myCid <- case optMyCid of
  None -> abort "myCid not found"
  Some myCid -> pure myCid

If you don’t care about the error message, you can also go for slightly simpler solution with a less helpful error:

Some myCid <- lookupByKey @myTemplate (key1, key2)
1 Like

Thank you @cocreature.
The long solution has solved my parse problem. (There’s an accidental typo in three places there: myCid → myCidOpt, inside the case; mentioning for the sake of a clean issue-solution history.)

Question related to the short solution you’ve mentioned:
The transaction will abort unless the lookupByKey returns a valid (isSome) ContractId - is that correct?

1 Like

Fixed the typo, only one of the places needs to be myCidOpt.

The short solution will indeed abort the transaction if you get a None, same as the long solution.

1 Like

@cocreature Thank you very much for the explanation.

1 Like

@cocreature is this something that could be caught by a dlint rule?

After making a similar (but slightly different) mistake as @mlyczak I added a dlint rule:

- warn: {lhs: "exercise (fromSome x) y", rhs: "case x of Some cid -> exercise cid y; None -> return()", note: "fromSome errors if the value is None. If you intend to optionally exercise, do a case match before exercising", name: Can't exercise None}

Curious if this is generally a good idea, and if not, why?

1 Like

The most plausible dlint rule for the original mistake that I could come up with is to warn on assertMsg … (isSome …) and suggest to use pattern matching instead. That seems fairly sensible for the most part although at least in a test, that type of code isn’t that unreasonable.

Your example is interesting. First, a small stylistic tip: You can use whenSome x (\cid -> exercise cid y) which is a bit shorter and I’d argue a bit easier to read (obviously that’s always somewhat subjective).

As for whether that rule is a good idea it obviously depends on your code but I’d argue that as a general rule, it is not a good idea. Yes, it clearly crashes in less cases. However is that a good thing? If the contract not being present is a case I didn’t expect (as indicated by the use of fromSome), I want my program to crash if it happens and not silently swallow the bug and do nothing. So while the rule results in a program that crashes less it changes the intent and could introduce new bugs instead of fixing them.

1 Like

Regarding the stylistic tip, when trying whenSome as suggested I get the following typecheck error:

Pattern syntax in expression context: cid -> exercise cid Cancel

It’s a good point w.r.t not wanting dlint to change the semantics although I believe the value of using dlint to educate users and help them avoid common pitfalls with immediate feedback in IDE outweighs the risk of semantic changes. As an example of pure user education, I also have:

- hint: {lhs: "fromSome x", rhs: 'fromSomeNote "error message" x', note: "in most cases you should prefer using fromSomeNote to get a better error on failures.", name: Prefer fromSomeNote over fromSome}

My recommendation is to add such rules as my Can't exercise None example as default dlint rules, but maybe as a hint instead of warn, and being even more verbose in the note regarding when to use this hint and when not. For example:

- hint: {lhs: "exercise (fromSome x) y", rhs: "case x of Some cid -> exercise cid y; None -> return()", note: "fromSome errors if the value is None. If this was your intention, ignore this hint. If your intention was to exercise only if a contract is passed to fromSome and skip the exercise if None is passed, use a case match before exercising", name: Can't exercise on None}
1 Like

Ah sorry, I typoed my example, it should be

whenSome x (\cid -> exercise cid y)

with the backslash for the lambda. I’ve fixed the original post to avoid confusion as well.

1 Like