Pre-RFC: Unpacking to Array and Tuple Literals

Discussions in the pull request thread of RFC 3723 - Static Function Argument Unpacking led to the conclusion, that defining unpacking into arrays and tuples would be a good idea before going forward with unpacking arguments.

I've started drafting a separate RFC for that extension and I think it's ready for a vibe check. Please note that it is quite heavily littered with todos, and the text is nowhere near ready to being submitted on GitHub, yet.

I couldn't get a hold of the author for the existing Pre-RFC: Array expansion syntax, but would like to thank them for their effort, which I've drawn a lot of inspiration from.

Edit 2025-03-16: Typo fixes, more coverage for other programming langs, made RFC template todos expandable for now.


Summary

This RFC extends RFC 3723 – Static Function Argument Unpacking by adding compile-time unpacking of tuples, tuple structs, and fixed-size arrays in definitions of tuple and fixed-size array literals. The syntax defined in RFC 3723 for argument unpacking, ...expr, is used inside the literal definition's parentheses or brackets as a shorthand for using the values of expr as the values defined for the literal in that place.

Motivation

RFC 3723 defines an MVP of a specific feature: argument unpacking. This RFC, on the other hand, defines a separate but related feature acknowledged as a future possibility with community interest in the former RFC's comment thread. While RFC 3723 enables constructing tuple structs, it does not provide a similar mechanism for building tuples. This RFC fixes that discrepancy.

Some benefits of unpacking into tuple and array literals include, for example:

  • Unpacking by hand is prone to copy-paste errors and typos, leading to syntactically correct erroneous code. For example, referring an incorrect index within the source array is easy to miss. This is exacerbated as collections containing more elements are unpacked. This RFC equips developers with a tool for avoiding that problem.
  • Code becomes shorter when unpacking in literals is used, as multiple consecutive accesses to fields in a collection can be replaced by a single occurrence of unpacking. Readability and maintainability are positively affected.
  • Code that previously repeated existing values already defined elsewhere, just to avoid the difficult-to-read syntax of accessing them manually, may become shorter and easier to change when switching to using these other collections by unpacking them.

Conjecture: Certain scientific and engineering fields involve code handling large arrays of numbers – so much so that languages such as MATLAB® have been created to fulfill these needs. For example, working with measurement data, digital signals, images and videos is often backed by code working on arrays. Unpacking into literals makes it more ergonomic to work with arrays in some situations, making Rust a more appealing choice for writing professional and consumer software involving any of the kinds of data listed above, such as video codecs, image manipulation software, and so on.

const Concatenation for Arrays

Unpacking into tuple and fixed-size array literals enables a way to concatenate arrays in const contexts – a feature with community appeal.

Initialization of Arrays Combining Specified and Default Values

This is somewhat similar to Functional Record Updates (FRU) / Struct Update Syntax.

Now:

static ARR: [u8; 255] = [0, 1, ...[0xff; 253]];

Before:

static ARR: [u8; 255] = {
    let mut arr = [0xff; 255];
    arr[0] = 0;
    arr[1] = 1;
    arr
};

(In non-const contexts, array::from_fn can be used)

let x: [u8; 5] = core::array::from_fn(|i| match i {
    0 => 0,
    1 => 1,
    _ => 0xff,
});

Conversions Between Tuples and Arrays

Unpacking into tuple and fixed-size array literals also provides a way to convert between tuples and arrays.

Guide-Level Explanation

To define fixed-size arrays and tuple literals in a way that uses the values from other such collections, these source collections can be unpacked within the definition of the new literal. This means that in such cases, the tedious alternative of accessing each item in the source collection one by one can be avoided.

TODO: Elaborate:

  • "what is being unpacked": the automatic using of all items in the "what" consecutively by syntactic sugar ...
  • "what is being unpacked": again as in rfc 3723, collections with sizes known during compilation time
  • "where is it unpacked to": this time, instead of function arguments as in rfc 3723, into definitions of fixed-size array and tuple literals
  • "when does unpacking occur": as in rfc 3723, during compilation, not run-time. This means unpacking can't panic.

Some things of note:

  • Unpacking is exhaustive, i.e. all values in the source collection are unpacked.
  • The items in the source are unpacked in the order in which they are in the source collection.

There are also certain limitations to this:

  • Unpacking into literals works during program compilation, not when the program is run. Consequently, in this MVP, it only works on certain types. As an upside, the program cannot panic due to this, and doing this is zero cost.

Simple Concatenation Example

To define a fixed-size array literal with values from another, unpack it thus:

/// A handful of numbers at the beginning of the Fibonacci sequence
const FIB5: [u8; 5] = [0, 1, 1, 2, 3];
/// A longer sequence of Fibonacci numbers (unpacking in literal definition)
const FIB10: [u8: 10] = [...FIB5, 5, 8, 13, 21, 34];

The old, manual way without unpacking into fixed-size array literals would have been:

/// A longer sequence of Fibonacci numbers (defined manually)
const FIB10: [u8: 10] = [FIB5[0], FIB5[1], FIB5[2], FIB5[3], FIB5[4], 5, 8, 13, 21, 34];

The alternative way above is also what the code would look like after the syntactic sugar of unpacking has been desugared.

TODO: Combining Defined Values with Default Values

const LEET_ONES: [u8; 64] = [1, 3, 3, 7, ...[1; 60]];

TODO: Many Unpackings in Same Definition Example

static RGB_MASK: [u64; 640 * 480] = [
    ...[0xFF00007F; 640 * 160],
    ...[0x00FF007F; 640 * 160],
    ...[0x0000FF7F; 640 * 160],
];

Transpiling C and C++ to Rust

C and C++ allow specifying arrays part-way, while the remaining elements are zeroed. Efforts such as c2rust could benefit from this RFC.

E.g., the following code (same in C and C++)

void foo() {
    int x[32] = {1, 2, 3, 4};
}

can now be transpiled to

fn foo() {
    let mut x: [libc::c_int; 32] = [1, 2, 3, 4, ...[0; 28]];
}

TODO: Unpacking a Byte String Example

Based on example given by scottmcm:

const HELLO_C: [u8; 14] = [...*b"Hello, world!", b'\0'];

or, further

TODO: check for inspiration: Magic number (programming) - Wikipedia

const FILE: [u8; 140] = [...*b"MZ", b'\0']; // ... TODO

Conversions between Arrays and Tuples

You can convert an array into a tuple:

let arr = ["cat", "dog", "rat", "bat"];
let my_tup = (...arr);

assert_eq!(my_tup, ("cat", "dog", "rat", "bat"));

Itertools has collect_tuple.

And a tuple into an array:

let tup = (1.00, 1.25, 1.50, 1.75, 2.00);
let my_arr = [...tup];

assert_eq!(my_arr, [1.00, 1.25, 1.50, 1.75, 2.00]);

Unpacking the Output of Const Functions

Consider the following program with the definition for WITH_PADDING omitted:

const fn frobnicate<const N: usize>(arr_in: [u8; N]) -> [u8; N] {
    let mut arr_out = arr_in;
    let mut i = 0;
    while i < N {
        arr_out[i] <<= 2;
        i += 1;
    }
    arr_out
}

// … definition of WITH_PADDING cut out …

fn main() {
    println!("{:#?}", WITH_PADDING);
}

WITH_PADDING can be defined thus, unpacking the results of the const function frobnicate in the middle of the array:

const WITH_PADDING: [u8; 5] = [0, ...frobnicate([1, 2, 3]), 0];

Before unpacking into array and tuple literals was possible, one or the other of following would have been needed:

const WITH_PADDING: [u8; 5] = [
    0,
    frobnicate([1, 2, 3])[0],
    frobnicate([1, 2, 3])[1],
    frobnicate([1, 2, 3])[2],
    0,
];
const WITH_PADDING: [u8; 5] = {
    let mut x = [0; 5];
    let result = frobnicate([1, 2, 3]);
    x[1] = result[0];
    x[2] = result[1];
    x[3] = result[2];
    x
};

Reference-Level Explanation

TODO: RFC template points

[!TIP]

This is the technical portion of the RFC. Explain the design in sufficient detail that:

  • Its interaction with other features is clear.
  • It is reasonably clear how the feature would be implemented.
  • Corner cases are dissected by example.

The section should return to the examples given in the previous section, and explain more fully how the detailed proposal makes those examples work.

The operator ... operates on the expression it is prefixed to, expr for short, by unpacking the operand.

Requirements for Unpackable exprs

What can be unpacked?

Anything that cannot be unpacked by hand, cannot be unpacked automatically either.

The exprs that can be unpacked have the following requirements:

  • It has a collection type, whose items can be accessed, in order, with indices starting from 0 and ending at length - 1.
  • Its size is known during program compilation.

For example, the following evaluate into possible operands for unpacking at compile time:

  • The following literals:
    • Array expressions.
    • Tuple expressions.
    • Byte string literals.
  • Fixed-size arrays.
  • Tuples.
  • Tuple structs.

As a corollary of the above rules, this MVP of unpacking into literals does not cause run-time panics.

Syntactic Sugar

When does unpacking happen?

Unpacking into literal definitions is syntactic sugar.

In a fixed-size array or tuple literal definition context, where n denotes the length/size/number of items in expr, ...expr desugars into consecutive comma-separated form of:

  • when unpacking an array: expr[0], expr[1], …, expr[n - 1] , or
  • when unpacking a tuple (struct): expr.0, expr.1, …, expr.(n - 1).

Basically, the above desugared forms are exactly the same as unpacking them by hand would be.

Syntax Details

Where can unpacking be used?

The exact same form of prefixing the expression that is unpacked with an operator, when in a specific context, with the same operator symbol, is selected as in RFC 3723. In short, this means using the three ASCII dot ellipsis ... as the operator.

The contexts where this applies are array expressions and tuple expressions.

Array expressions are changed by including ...Expression as an alternative in ArrayElements for both of the two comma-separated _Expression_s .

Tuple expressions are changed by including ...Expression as an alternative in TupleElements for both of the two comma-separated _Expression_s .

Building tuple structs this way is already supported by changes introduced in RFC 3723, since those are built using a constructor function that can be called using argument unpacking.

Errors

No new errors arise from the use of unpacking into tuple and array literals. However, existing errors may take the syntactic sugar into account, if necessary. The erroneous situations in the subchapters below demonstrate errors already handled by the compiler, but shown in the context of the new feature.

Length Mismatch

No attempt is made as part of this feature to (type-)check the surroundings for possible constraints to how many items can be unpacked. Any compilation errors arising from attempting to unpack an incorrect-size collection follow automatically from how these errors are already handled by the compiler.

E.g., the following two will fail due to the same error – attempting to define a type of length-2 array to have a length-4 array:

let src: [i8; 4] = [123; 4];
let dst: [i8; 2] = [...src];
//            ^ note difference in length
let src: [i8; 4] = [123; 4];
let dst: [i8; 2] = [src[0], src[1], src[2], src[3]];
//            ^ note difference in length

Error message for latter (where the source array is manually unpacked):

error[E0308]: mismatched types
 --> src/main.rs:3:24
  |
3 |     let dst: [i8; 2] = [src[0], src[1], src[2], src[3]];
  |              -------   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected an array with a size of 2, found one with a size of 4
  |              |    |
  |              |    help: consider specifying the actual array length: `4`
  |              expected due to this

Error message for former (with unpacking syntax):

error[E0308]: mismatched types
 --> src/main.rs:3:24
  |
3 |     let dst: [i8; 2] = [...src];
  |              -------   ^^^^^^^^ expected an array with a size of 2, found one with a size of 4
  |              |    |
  |              |    help: consider specifying the actual array length: `4`
  |              expected due to this

Cyclic Definitions

Rust compiler already handles the desugared form with cyclic definitions correctly. Consequently, the following two examples where an array is defined using the other and vice versa – using unpacking or without it – will fail due to the same error:

const A: [u8; 1] = [...B];
const B: [u8; 1] = [...A];
const A: [u8; 1] = [B[0]];
const B: [u8; 1] = [A[0]];

Error message with latter (where the source array is manually unpacked):

error[E0391]: cycle detected when simplifying constant for the type system `A`
 --> src/main.rs:1:1
  |
1 | const A: [u8; 1] = [B[0]];
  | ^^^^^^^^^^^^^^^^
  |
note: ...which requires const-evaluating + checking `A`...
 --> src/main.rs:1:21
  |
1 | const A: [u8; 1] = [B[0]];
  |                     ^
note: ...which requires simplifying constant for the type system `B`...
 --> src/main.rs:2:1
  |
2 | const B: [u8; 1] = [A[0]];
  | ^^^^^^^^^^^^^^^^
note: ...which requires const-evaluating + checking `B`...
 --> src/main.rs:2:21
  |
2 | const B: [u8; 1] = [A[0]];
  |                     ^
  = note: ...which again requires simplifying constant for the type system `A`, completing the cycle
  = note: cycle used when running analysis passes on this crate
  = note: see https://rustc-dev-guide.rust-lang.org/overview.html#queries and https://rustc-dev-guide.rust-lang.org/query.html for more information

Error message for former (with unpacking syntax):

error[E0391]: cycle detected when simplifying constant for the type system `A`
 --> src/main.rs:1:1
  |
1 | const A: [u8; 1] = [...B];
  | ^^^^^^^^^^^^^^^^
  |
note: ...which requires const-evaluating + checking `A`...
 --> src/main.rs:1:21
  |
1 | const A: [u8; 1] = [...B];
  |                        ^
note: ...which requires simplifying constant for the type system `B`...
 --> src/main.rs:2:1
  |
2 | const B: [u8; 1] = [...A];
  | ^^^^^^^^^^^^^^^^
note: ...which requires const-evaluating + checking `B`...
 --> src/main.rs:2:21
  |
2 | const B: [u8; 1] = [...A];
  |                        ^
  = note: ...which again requires simplifying constant for the type system `A`, completing the cycle
  = note: cycle used when running analysis passes on this crate
  = note: see https://rustc-dev-guide.rust-lang.org/overview.html#queries and https://rustc-dev-guide.rust-lang.org/query.html for more information

Lints

It is conceivable that unpacking is, by accident, used in situations the user did not intend to. Compile-time warnings are output for the following cases:

  • Defining an array wholly by unpacking just one source array, or a tuple by wholly unpacking just one source tuple:

    let y = [...x];
    
    • Lint: shorter way to express is let y = x;
  • Unpacking an empty source:

    let src: [i8; 0] = [];
    let dst = [5, ...src];
    
    • Lint: attempting to unpack a collection with zero elements has no effect.
    • This is the same warning raised when unpacking a zero-length array as function arguments, as per RFC 3723.

Interactions with Tools

Rustfmt

...expr within an array or tuple definition is formatted as if it forms an expr.

Rustdoc

Unpacking does not lead to any new documentable items or change existing ones.

Rust-Analyzer

Support for new syntax is required.

Code actions for using unpacking may be implemented.

Cargo-semver-checks

No interactions. Unpacking relates to using APIs, not defining them.

Drawbacks

  • If we want to implement a future possibility of run-time unpacking of dynamically sized types, having both the unpacking operator ... and the range sugar .. appear close to each other may look confusing. E.g., when unpacking into an array this way: [...my_arr[idx..idx2], ...other_slice[a..b]].
  • However useful this syntactic sugar may be, it is additional syntax to maintain by the project, support in the compiler, account for when planning new features, handle by other software working with Rust code, teach in the documentation, understand by the developers, and so on.
  • We may want to implement array concatenation in some other way entirely, and having the syntactic sugar of unpacking into array literals provides a duplicate feature.

Rationale and Alternatives

TODO: RFC template points

[!TIP]

  • Why is this design the best in the space of possible designs?
  • What other designs have been considered and what is the rationale for not choosing them?
  • What is the impact of not doing this?
  • If this is a language proposal, could this be done in a library or macro instead? Does the proposed change make Rust code easier or harder to read, understand, and maintain?

Use of the Same Syntax as for Argument Unpacking

Since unpacking in literal definitions is very closely related to argument unpacking, choosing a different syntax for each would most likely cause confusion. Unless the community specifically wants to diverge in syntax for these two cases, the symbol, whether it is prefixed or suffixed, etc. are best bikeshed in the comment thread of RFC 3723.

Other programming languages that have both of these features also usually use the same syntax for both.[citation needed]

Not Implementing This Feature

In some contexts, arrays can simply be concatenated with existing functions.

Implementing compile-time array concatenation is somewhat tricky, but can be done.

Const generics allow some tricks in defining arrays: Concatenating arrays - #5 by jbe - help - The Rust Programming Language Forum

It's possible to write macros to concatenate arrays: Concatenating arrays - #9 by scottmcm - help - The Rust Programming Language Forum

The above tricks can be provided by a crate.

build.rs can also be used.

Arguably, none of the available workarounds leave a particularly developer-friendly impression, while the syntactic sugar of unpacking in array and tuple literals is intuitive and, compared to macro, build.rs, and external crate solution, reduces compilation times.

One of the main benefits is ergonomics, and having to invoke macros has some overhead due to the additional boilerplate, while a bespoke syntax does not have this problem.

E.g., for vectors, you can .extend.

Concatenate with +

Some languages, such as Python and Ruby, allow concatenating arrays with the infix + operator (see examples below under Prior Art). However, even in Python ecosystem, for example in NumPy, the + symbol between array arguments has a different meaning: it does elementwise addition of arrays of the same size. For example:

>>> arr_a = np.array([1, 2, 3])
>>> arr_b = np.array([4, 5, 6])
>>> arr_c = arr_a + arr_b
>>> arr_c
array([5, 7, 9])

MATLAB® and Octave also use infix + for addition:

octave:1> a = [1, 2, 3]
a =

   1   2   3

octave:2> b = [4, 5, 6]
b =

   4   5   6

octave:3> c = a + b
c =

   5   7   9

Selecting the syntax of infix + for array concatenation in Rust could cause confusion and would conflict with possible uses of mathematical symbols for other purposes, such as elementwise addition. Moreover, focusing on concatenation does not help with the other issues unpacking provides solutions for.

Prior Art

TODO: RFC template points

[!TIP]

Discuss prior art, both the good and the bad, in relation to this proposal. A few examples of what this can include are:

  • For language, library, cargo, tools, and compiler proposals: Does this feature exist in other programming languages and what experience have their community had?
  • For community proposals: Is this done by some other community and what were their experiences with it?
  • For other teams: What lessons can we learn from what other communities have done here?
  • Papers: Are there any published papers or great posts that discuss this? If you have some relevant papers to refer to, this can serve as a more detailed theoretical background.

This section is intended to encourage you as an author to think about the lessons from other languages, provide readers of your RFC with a fuller picture. If there is no prior art, that is fine - your ideas are interesting to us whether they are brand new or if it is an adaptation from other languages.

Note that while precedent set by other languages is some motivation, it does not on its own motivate an RFC. Please also take into consideration that rust sometimes intentionally diverges from common language features.

RFC 3723 discusses the specific feature introduced in this RFC in its top Future Possibility subchapter Unpacking in Fixed-Size Array and Tuple Literals.

Past Proposals

Two major proposals, in the form of pre-RFCs by nwn on Rust Internals, lay out features similar/same to this RFC:

TODO: This RFC (as well as RFC 3723) acts on the "mirror side" of variadics, and thus this unpacking feature is typically mentioned in variadic generics initiatives

Crates

  • velcro provides an alternative vec! macro that uses the unary .. prefix operator for unpacking collection into the vector.
  • lit_vek provides the vek! macro, a drop-in replacement for std::vec! that also features the ...arr syntax for unpacking iterables into the vector.

Other Programming Languages and Unpacking

Several other programming languages have this same (or a very similar) feature, allowing unpacking contents of collections into other collections. Some have discussed adding such feature.

TODO: Probably others too... Find out.

Python

PEP 448: PEP 448 – Additional Unpacking Generalizations | peps.python.org

>>> a = [1, 2, 3]
>>> b = [4, 5, 6]
>>> c = [*a, *b]
>>> c
[1, 2, 3, 4, 5, 6]

Note that Python (like Ruby) allows concatenating lists with a different syntax as well (continuing from above example):

>>> d = a + b
>>> d
[1, 2, 3, 4, 5, 6]

Ruby

Splat operator in implicit array assignment: https://docs.ruby-lang.org/en/3.4/syntax/assignment_rdoc.html#label-Implicit+Array+Assignment

irb(main):001:0> a = [1, 2, 3]
=> [1, 2, 3]
irb(main):002:0> b = [4, 5, 6]
=> [4, 5, 6]
irb(main):003:0> c = [*a, *b]
=> [1, 2, 3, 4, 5, 6]

Note that Ruby (like Python) allows concatenating lists with a different syntax as well (continuing from above example):

irb(main):004:0> d = a + b
=> [1, 2, 3, 4, 5, 6]

Go

Other Programming Languages and a Subset of Features

Some languages have alternative features comprising a subset of the possibilities enabled by unpacking as described in this RFC.

C and C++

For example, both C and C++ allow initializing arrays combining selected and default zeroed values, e.g.:

// sets indices 0, 1, 2 and 3 to values below; rest to zeroes
int x[32] = {1, 2, 3, 4};

Or in a C99 extension (not supported by C++):

// sets index 4 to value below; rest to zeroes
int x[32] = { [4] = 5 };

D

D allows array initialization with selected and default values as well:

int[5] a = [ 1:2, 3:4 ];
assert(a == [0, 2, 0, 4, 0]);

In D, arrays can be concatenated with the binary infix ~ operator (the cat operator), e.g.:

int[] a = [1, 2];
int[] b = a ~ [3, 4];
assert(b == [1, 2, 3, 4]);

Unresolved Questions

TODO: RFC template points

[!TIP]

  • What parts of the design do you expect to resolve through the RFC process before this gets merged?
  • What parts of the design do you expect to resolve through the implementation of this feature before stabilization?
  • What related issues do you consider out of scope for this RFC that could be addressed in the future independently of the solution that comes out of this RFC?
  • Would this already support unpacking into slice definitions as well, without extra effort?

Future Possibilities

TODO: RFC template points

[!TIP]

Think about what the natural extension and evolution of your proposal would be and how it would affect the language and project as a whole in a holistic way. Try to use this section as a tool to more fully consider all possible interactions with the project and language in your proposal. Also consider how this all fits into the roadmap for the project and of the relevant sub-team.

This is also a good place to "dump ideas", if they are out of scope for the RFC you are writing but otherwise related.

If you have tried and cannot think of any future possibilities, you may simply state that you cannot think of anything.

Note that having something written down in the future-possibilities section is not a reason to accept the current or a future RFC; such notes should be in the section on motivation or rationale in this or subsequent RFCs. The section merely provides additional information.

The future possibilities are largely the same as in RFC 3723, although some of the concepts listed there can be specifically adapted into the context of unpacking into collections, which this RFC is the MVP for.

Unpacking in Standard Library Macros

Supporting unpacking of collections in standard library macro invocations, especially when creating vectors with vec![], is expected to be allowed when this RFC lands.

See crates velcro and lit-vek listed under Prior Art subchapter Crates.

Dynamic Contexts

Run-time unpacking of dynamically sized types (DST) into arrays and tuples.

The following collections matching this have a clearly defined order for the elements they hold:

  • Unpacking of slices
  • Unpacking of vectors

This effort would likely go hand in hand with run-time argument unpacking of DSTs.

See crates velcro and lit-vek listed under Prior Art subchapter Crates.

General Unpacking Operator

This effort would also likely go hand in hand with run-time argument unpacking.

3 Likes

As I said in the RFC 3723 discussion thread, this feature belongs as part of that RFC, not as a separate RFC. Posting it separately would just mean that discussion about syntax, etc will be split between two places. That would just be a hassle for everyone.

That is a concern in having two separate RFCs, and I did put some serious thought into whether having two texts anyway would be worth that risk. Regardless of if the text in this pre-RFC ends up as part of RFC 3723 or as a separate one, I found that by far the fastest and easiest way to produce the text itself is by writing it separately – at least at first.

Anyway, I feel I need to note that RFC 3723 (where work still remains to be done) is not short, and this quite unfinished pre-RFC itself is already around 16 pages. Thus, I'm quite hesitant to commit to describing the features in the same RFC.

2 Likes

This has probably been said before, but I think using ... is absolutely untenable, especially considering ..[1, 2, 3] is already valid syntax, and ... used to be used for inclusive ranges pre-1.0.

There are plenty of languages that don't use ... for this, I think it would be much better for readability to copy one of them, or use a magic macro for this, or even a soft keyword! (if raw can be a soft keyword, so can unpack).

1 Like

Thanks! I'll elaborate on the alternatives in the linked RFC 3723 a bit more, as I consider that the "main" RFC regarding the syntax choice. I don't recall seeing the suggestion for a soft keyword previously, e.g. unpack as you mentioned, even though that seems like a good alternative (Lisps and Lua stand out from the others with a similarish approach, as documented in Table 2 of that RFC).

scottcmc

did you mean @scottmcm?

2 Likes

Yes, thanks for spotting. Corrected in the latest version edited to the original post.