Stable Cadence Preview Release #1

The Cadence team is excited to share the first preview release of Stable Cadence with the Cadence developer community.

To learn more about Stable Cadence, have a read through the announcement post: https://forum.onflow.org/t/the-path-to-stable-cadence/2702

This first preview release contains the changes that have been proposed through FLIPs and have been implemented so far.
This includes the changes we announced in the last progress update in the Ready/Merged section: https://forum.onflow.org/t/another-update-on-stable-cadence/3715, as well as 3 additional changes that were completed since:

Previously in the “Upcoming” section:

References to resource-kinded values get invalidated when the referenced value is moved

Click here to read more

https://github.com/onflow/flow/pull/1043

https://github.com/onflow/cadence/pull/2037

https://github.com/onflow/cadence/pull/1999

Previously, when a reference is taken to a resource, that reference remains valid even if the resource was moved, for example when created and moved into an account, or moved from one account into another.

In other words, references to resources stayed alive forever. This could be a potential safety foot-gun, where one could gain/give/retain unintended access to resources through references.

In Stable Cadence, references are invalidated if the referenced resource is moved after the reference was taken. The reference is invalidated upon the first move, regardless of the origin and the destination.

e.g:

// Create a resource.
let r <-create R()

// And take a reference.
let ref = &r as &R

// Then move the resource into an account.
account.save(<-r, to: /storage/r)

// Update the reference.
ref.id = 2

Old behaviour:

// This will also update the referenced resource in the account.
ref.id = 2

In Stable Cadence, the above operation will result in a static error.

// Trying to update/access the reference will produce a static error:
//     "invalid reference: referenced resource may have been moved or destroyed"
ref.id = 2

However, not all scenarios can be detected statically. e.g:

pub fun test(ref: &R) {
    ref.id = 2
}

In the above function, it is not possible to determine whether the resource to which the reference was taken has been moved or not. Therefore, such cases are checked at run-time, and a run-time error will occur if the resource has been moved.

Semantics for variables in for-loop statements changed

Click here to read more

https://github.com/onflow/flips/pull/13

https://github.com/onflow/cadence/pull/2024

The behaviour of variables in for-loop statements changed. This removed an often surprising behaviour from the language and reduced the likelihood of bugs. This change only affects few programs, as the behaviour change is only noticeable if the program captures the for-loop statement variables in a function value (closure).

// Capture the values of the array [1, 2, 3]
let fs: [((): Int)] = []
for x in [1, 2, 3] {
    // Create a list of functions that return the array value
    fs.append(fun (): Int {
        return x
    })
}

// Evaluate each function and gather all array values 
let values: [Int] = [] 
for f in fs {
    values.append(f())
}

Previously, values would result in [3, 3, 3], which might be surprising and unexpected. This is because x was reassigned the current array element on each iteration, leading to each function in fs returning the last element of the array.

In Stable Cadence, values will result in [1, 2, 3], which is likely what the author of the program expected.

New:

Syntax for function types changed

Click here to read more

https://github.com/onflow/cadence/pull/2140

Previously, function types were expressed using a different syntax from function declarations or expressions.

e.g:

pub fun foo(n: Int8, s: String): Int16 { /* ... */ }
// function `foo` had the type `((Int8, String): Int16)`

The previous syntax was unintuitive for developers - the problem becomes more apparent in more complex type signatures. Nested function types led to deeply nested parentheses and discouraged smart contract writers from utilizing higher-order functions.

pub fun filter(f: ((A): Bool), xs: [A]): [A] { /* ... */ }
// function `filter` had the type `((((A): Bool), [A]): A)`

In Stable Cadence, function types are expressed using the fun keyword, just like expressions and declarations. This improves readability and makes function types more obvious. The previous examples are now written as:

pub fun foo(n: Int8, s: String): Int16 { /* ... */ }
// function `foo` has the type `fun(Int8, String): Int16`

pub fun filter(f: fun(A): Bool, xs: [A]): [A] {...}
// function `filter` has the type `fun(fun(A): Bool, [A]): [A]`

The : token is right-associative, so functions that return other functions can have their types written without nested parentheses:

fun curriedAdd(_ x: Int): fun(Int): Int {
  return fun(_ y: Int): Int {
    return x + y
  }
}
// function `curriedAdd` has the type `fun(Int): fun(Int): Int`

To bring function types closer to function expressions, we now allow return types for procedures (functions with no explicit return type, i.e. Void) to be omitted in types too. A function type without an explicit return type annotation will now default to returning Void.

pub fun logTwice(_ value: AnyStruct) { // return type is inferred to be `Void`
  log(value)
  log(value)
}

// these types are equivalent
let logTwice1: fun(AnyStruct): Void = logTwice
let logTwice2: fun(AnyStruct) = logTwice

For contracts containing function-typed values with explicit type annotations, the old syntax will now throw an error during parsing. Developers can migrate their contracts by adding the fun keyword to these type annotations, for example:

// before
let baz: ((Int8, String): Int16) = foo

// after
let baz: (fun (Int8, String): Int16) = foo

// the surrounding parentheses are no longer required
let baz: fun (Int8, String): Int16 = foo 

As a bonus consequence of these changes to the language syntax, we now allow any type to be parenthesized. This is useful for complex type signatures, or for expressing optional functions:

// a function returning an optional Int16
let optFun1: fun (Int8): Int16? = 
    fun (_: Int8): Int? { return nil }

// an optional function returning an Int16
let optFun2: (fun (Int8): Int16)? = nil

Preview release installation

You can install this preview release using the following update command:

sh -ci "$(curl -fsSL <https://storage.googleapis.com/flow-cli/install.sh>)" -- v0.41.3-stable-cadence-5

Please start evaluating this preview release with your Cadence contracts.

The Cadence team is eager to receive your feedback! How do you feel about the proposed breaking changes? Did you have any problems upgrading and running your contracts? Did you find any bugs?

Note that this is only a first preview release, and more changes are planned for the final Stable Cadence release.

I tried updating overflow to use this new flow-cli/flowkit version, but when I run the tests i get an error deploying the core NonFungibleToken contract. Can anybody advice?

PR is here: https://github.com/bjartek/overflow/pull/95

error: Impure operation performed in view context
    	            	   --> f8d6e0586b0a20c7.NonFungibleToken:141:12
    	            	    |
    	            	141 |             result.getIDs().length == 0: "The created collection must be empty!"
    	            	    |             ^^^^^^^^^^^^^^^

Like many contracts, also the NFT standard is affected by the Stable Cadence changes.

The NFT contract updated for Stable Cadence is available on this feature branch:

Both the NonFungibleToken contract and the MetadataViews contract need to be changed:

I have fixed the outstanding bugs in overflow so if you want to use it to test with this version of the emulator use the stable-cadence branch that is here https://github.com/bjartek/overflow/tree/stable-cadence

NB! You have to ensure you use the new versions of NFT/MW that bastian linked above yourself.