大家好!欢迎来到我关于Cadence的双周博客,Cadence是Flow区块链的新智能合约编程语言。如果你是一个新人,请查看我的介绍性博文,以便开始学习!
今天,我将谈论Cadence的一个方面,许多人在完全理解他们的想法方面有很多困难。选修课!
选项就像编程语言中的薛定谔之猫。
或者他们是吗?
对不起,不好的比喻,但当你读完后,你会明白我的来意。
什么是期权?
Cadence中的选项是在编程语言中使用变量的一种安全而强大的方式。不过,它们可能很难被熟悉。
选项词要么说 "有一个值,它是x",要么说 "根本就没有一个值"。所以这个值要么已经被设置了,要么更重要的是没有被设置。当选项没有其特定类型的值时,默认为nil(技术上来说是一个值)。
任何类型都可以是一个可选类型。你可以通过在常规变量声明后使用"?"来声明一个可选类型,像这样。
When assigning to an optional, you can assign to it as if the type was non-optional:
but you can also assign the value nil to the optional.
Why would I want to say something is nil?
Optionals allow software to be able to handle error cases and similar situations more gracefully.
The main goal is to make the unavailabe/empty/nothing state of a variable explicit, and even more so, make it explicit when something cannot ever be unavailabe/empty/nothing.
In languages that allow any variable to be null, it’s often not clear if the variable could be null at the specific location of the code, which leads to two problems:
- Either the null case isn’t handled by the programmer, and then there is a null value, causing a null-dereference, i.e. crash;
- Or the null case is considered by the programmer, and they add defensive checks to handle the supposed null-case, though it actually never occurs, causing unnessary overhead.
Here is an example:
Lets say we are using the field that I defined above in a contract, but not as an optional:
If the value hasn’t been set yet, but someone wants to read it, how do we communicate that it hasn’t been set? We could just return an empty string, but that is effectively the same as returning any non-empty string, so the reader might not know that an empty string means that it hasn’t been set yet and make a mistake assuming that everything is ok.
If we make the field optional:
Then it becomes very clear to the reader. If they try to read name and they get nil back, then they can easily handle the error and do whatever they need to do in that case. There is no confusion there. nil means nil.
In Cadence, fields in composite types are required to be initialized, so this exact problem won’t come up very often, but you will often see similar cases that are slightly more complex.
For example, the values in dictionaries are all optionals. You can initialize a dictionary to be empty like this:
But since there are an infinite possible number of key-value pairs, if you try to access any one of them, it will return an optional, even if the value exists for the key you provide!
How do I handle Optionals?
If left to their own devices, optionals can be a handful!
Values of optional variables are still considered to be wrapped in the optional. “Wrapped” is a term that means that we still don’t know whether or not the value is nil or not. If you try to perform a normal operation that you could usually perform with the type you are using, it will fail the Cadence type checker:
Even though both arguments specify some form of Int , xis still wrapped, so the program doesn’t actually know if it is nil or not. It won’t let us perform a mathematical operation with it because you can’t add nil and a number! That would explode the universe!!!!
You need to unwrap the value from its optional first, then perform the operation.
This also applies to composite types. If you have a composite type that is optional, you need to unwrap the optional before you can call any of its methods or access any of its fields.
Here is a simple example:
So if you see an error like this:
Look for the ? at the end of the type name. The object is often still a wrapped optional and it needs to be unwrapped before calling the hello method. (better error messages are also coming in the future) 😃
Unwrapping Optionals
There are multiple different ways to unwrap optionals, and they all have different behavior and are used for different use-cases.
Nil-Coalescing Operator
The nil-coalescing operator ?? returns the value inside an optional if it contains a value, or returns a specified alternative value if the optional has no value, i.e., the optional value is nil.
If the left-hand side is non-nil, the right-hand side is not evaluated.
See the Cadence docs for more info about the nil-coalescing operator.
You probably will see this used a lot when borrowing references to capabilities and resources. It is possible to put any code on the right side of the ?? so if a borrow fails, it returns an optional that is nil , but in many cases, if a borrow fails, then the transaction should fail. The nil-coalescing operator allows us to panic and print an error message so the state is reverted and the caller knows exactly what went wrong.
For example, when we borrow a reference to an account’s FlowToken receiver, we usually use this:
If borrow succeeds, it returns a valid reference. If it fails, it returns nil . Therefore, the type of the return value is &{FungibleToken.Receiver}? and we need to unwrap it before we can use it.
Force-Unwrap
Another more succinct way of unwrapping an optional value is to use the force-unwrap operator (! ). The force unwrap will get the value of the optional if it exists, or it will panic and abort if it doesn’t.
See the Cadence docs for more information and examples of force-unwrap.
If a force-unwrap fails, it only prints a generic error message about failing when trying to force unwrap. Because of this, you should be very careful about where you use force-unwrap, because if it fails, it will be much harder for you to determine where the error is coming from.
You should only use the force-unwrap operator in places where you are absolutely sure that the value isn’t going to be nil . For example, if you have already checked that it isn’t nil in a pre-condition, then you know that when you force-unwrap, you’ll be safe.
Optional Binding
Another powerful way to unwrap and handle optionals is via optional binding, a method that allows you to include the unwrapping in a conditional that executes different blocks of code depending on how the unwrapping went.
Optional Biding is a variant of the if-statement that conditionally executes a body of code if an optional is nil or not.
See the cadence docs for examples of optional binding
Optional binding is a thorough way to handle different cases for optionals, because it gives you the most flexibility with how your program can behave.
If nil is an expected potential value for an optional in your program, and you have many operations you need to perform in that case, then optional binding is the way to go.
Optional Chaining
Last but not least, we have optional chaining. Sometimes, there are composite types that are optional, because they are stored in a dictionary, or are returned as optionals from a function that retrieves them. If you want to call a function on an optional composite type, you can use optional chaining to “optionally” call that function. If the object you are calling is nil , it will just not execute any function and move on and return nil .
See the Cadence docs for more info and examples.
Conclusion
Optionals are an important feature of many languages and learning to use them properly is important for writing clean and secure code so make sure you understand this well!
I hope this post has been informative and useful!
Flow Discord: https://discord.gg/flow
Flow Forum: https://forum.onflow.org
Flow Github: https://github.com/onflow/flow
See you next week! 👋
Thank you to the cats of Axoim Zen for starring in this post!
And thank you to Bastian and Supun for reviewing this, plus Alyssa for the amazing graphics!