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)
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?
@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?
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.
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}