Chapter 4 Lesson 5 - Miscellaneous
Heeeeello again and welcome back to the last lesson in this course. Although you have probably gotten sick of me, I know you’re going to miss me.
In this lesson, I’m going to be throwing a lot of random, smaller topics at you just for the heck of it. These things are not crazy important, but will help you write your programs more efficiently.
Nil-Coalescing Operator
You know how when we write panic
statements sometimes, we use the ??
symbol? Well, what actually is that?
The ??
allows you to perform some action if the thing to the left of it is equal to nil
. Let’s look at an example:
var var1 = nil
var var2 = var1 ?? 1
log(var2) // 1
In this case, because var1
is nil
, it will instead be 1. Let’s look at another example:
var var1 = 2
var var2 = var1 ?? 1
log(var2) // 2
Here, beacuse var1
is not nil
, it will just equal 2. Doesn’t it make sense now why we can use ??
after we borrow references from account storage? Something like this:
pub fun main(user: Address) {
let attemptBorrow: &ExampleNFT.Collection{NonFungibleToken.CollectionPublic? = getAccount(user).getCapability(/public/Collection)
.borrow<&ExampleNFT.Collection{NonFungibleToken.CollectionPublic}>()
let collection = attemptBorrow ?? panic("The user does not have a Collection set up.")
}
Optional Binding
Optional Binding is another really useful strategy we can use with optionals. Using a new if let
syntax, it lets you enter an if statement depending on whether or not a value is nil, and unwraps it if so:
let var1: Int? = nil
if let var2 = var1 {
// it never makes it in here
} else {
// it will go to here
}
let var1: Int? = 2
if let var2 = var1 {
// it will go in here, and `var2` will now have
// type `Int`, not `Int?`. So it unwrapped the
// optional.
//
// `var2` = 2
} else {
// it never makes it in here
}
The above is just a simple example of what optional binding is doing. However, there are two main use cases I use optional binding for:
- Indexing into a dictionary
let dictionary: {String: Int} = {"One": 1, "Two": 2, "Three": 3}
if let number = dictionary["One"] {
// dictionary["One"] is a `Int?` type,
//and it gets unwrapped into `number`
//which has type `Int`
log(number) // 1
}
- Borrowing from someone’s account storage
let token: @ExampleNFT.NFT <- create NFT()
if let collection = getAccount(0x01).getCapability(/public/Collection).borrow<&ExampleNFT.Collection{NonFungibleToken.CollectionPublic}>() {
// the user's collection is set up
collection.deposit(token: token)
return nil
}
// the collection was not set up because we didn't return in the if statement
return <- token
Optional Chaining
Optional chaining is actually quite complicated to explain, but I will do my best here. Basically, optional chaining is useful for when we want to access a variable on a certain object that is an optional type, but don’t want to panic if the object is nil
.
For example:
pub struct Test {
pub let id: Int
init() {
self.id = 1
}
}
pub fun main() {
let test1: Test? = Test()
let test2: Test? = nil
let number1 = test1.id // ERROR: "Cannot access `id` on `Test?` type
let number2: Int = test1!.id // 1
let number3 = test2!.id // PANIC on the force-unwrap of an optional
// Using Optional Chaining
let number4: Int? = test1?.id // 1
let number5: Int? = test2?.id // nil
}
Hopefully this script allows you to see the difference. When we have a struct or resource (in this case a struct Test
), and want to access a field on it, we can actually use the ?
operator to attempt to access the field without panic
-ing. If the struct is nil
, it will simply return nil
. If not, it will return the underlying value as an optional type.
Conditional (Ternary) Operator
This one is actually quite easy, and is popular in many languages like Javascript.
Sometimes, we write code like this:
var var1 = "one"
var var2 = 0
if (var1 == "one") {
var2 = 1
} else if (var1 == "two") {
var2 = 2
} else {
var2 = 3
}
It’s a really silly example, but is actually pretty annoying to write. Instead, we can use a “conditional operator,” often written with a bunch of ?
and :
symbols, like this:
var var1 = "one"
var var2 = var1 == "one" ? 1 : var1 == "two" ? 2 : 3
These two mean the exact same thing. When you use conditional operators, it usually goes something like:
(boolean statement #1) ?
(if [boolean statement #1], this) :
(else, boolean statement #2) ?
(if [boolean statement #2], this) :
...
For a potentially better explanation, see here.
Quests
- Assuming the user’s collection is set up properly, what will be returned from this function?
import ExampleNFT from 0x01
import NonFungibleToken from 0x02
pub fun main(): [UInt64]? {
let token: @ExampleNFT.NFT <- create NFT()
if let collection = getAccount(0x01).getCapability(/public/Collection).borrow<&ExampleNFT.Collection{NonFungibleToken.CollectionPublic}>() {
return collection.getIDs()
}
return nil
}
- Assuming the user’s collection was not set up properly, what will be returned from this function?
import ExampleNFT from 0x01
import NonFungibleToken from 0x02
pub fun main(): [UInt64]? {
let token: @ExampleNFT.NFT <- create NFT()
if let collection = getAccount(0x01).getCapability(/public/Collection).borrow<&ExampleNFT.Collection{NonFungibleToken.CollectionPublic}>() {
return collection.getIDs()
} else {
return []
}
return nil
}
- Assuming the user’s collection was not set up properly, what will be returned from this function?
import ExampleNFT from 0x01
import NonFungibleToken from 0x02
pub fun main(): [UInt64]? {
let token: @ExampleNFT.NFT <- create NFT()
if let collection = getAccount(0x01).getCapability(/public/Collection).borrow<&ExampleNFT.Collection{NonFungibleToken.CollectionPublic}>() {
return collection.getIDs()
}
return nil
}
- What are the two outcomes that could happen when I run this script? Explain each.
import ExampleNFT from 0x01
import NonFungibleToken from 0x02
pub fun main(user: Address): [UInt64] {
let collection = getAccount(user).getCapability(/public/Collection).borrow<&ExampleNFT.Collection{NonFungibleToken.CollectionPublic}>()
return collection!.getIDs()
}
- What is wrong with the below script?
- a) Please fix it (you are not allowed to modify this line:
return collection?.getIDs()
). - b) After you fix it, what are the two possible outcomes that could happen when you run the script? Explain each.
import ExampleNFT from 0x01
import NonFungibleToken from 0x02
pub fun main(user: Address): [UInt64] {
let collection = getAccount(user).getCapability(/public/Collection).borrow<&ExampleNFT.Collection{NonFungibleToken.CollectionPublic}>()
return collection?.getIDs()
}
- Write the below code in
if-else
format instead.
let vault = getAccount(user).getCapability(/public/Vault).borrow<&FlowToken.Vault{FungibleToken.Receiver}>()!
var status = vault.balance >= 10000 ? "Flow Legend" : vault.balance < 10 ? "Needs More" : vault.balance > 5000 ? "Flow Believer" : "Unknown"
- Explain a potential benefit of using conditional chaining as opposed to force unwrapping an optional.