-4

I have tried the following snipets in Python

l1 = [0,1]
l2 = [0,1]
a, b = 0, 1
(l1[a], l1[b], l2[l1[a]], l2[l1[b]]) = (l1[b], l1[a], a, b)
print (l1)
print (l2)

The result is:

[1, 0]
[1, 0]

However this is what I expected: First, a,b is plugged in the whole expression as

(l1[0], l1[1], l2[0], l2[1]) = (l1[1], l1[0], 0, 1)

Then finally it will print:

[1, 0]
[0, 1]

Same on Rust,

fn main() {
    let mut l1 = [0,1];
    let mut l2 = [0,1];
    let (a, b) = (0, 1);
    (l1[a], l1[b], l2[l1[a]], l2[l1[b]]) = (l1[b], l1[a], a, b);
    println!("{:?}", l1);
    println!("{:?}", l2);
}

prints

[1, 0]
[1, 0]

My guess on this behavior is: only the right expression is evaluated

(l1[a], l1[b], l2[l1[a]], l2[l1[b]]) = (1, 0, 0, 1)

then the assignments are done serially:

l1[a] = 1
l1[b] = 0
l2[l1[a]] = 0 #l2[1] = 0
l2[l1[b]] = 1 #l2[0] = 1

Why is this happened?

garam
  • 1
  • 1
  • Does this answer your question? [Multiple assignment and evaluation order in Python](https://stackoverflow.com/questions/8725673/multiple-assignment-and-evaluation-order-in-python) – Abdul Aziz Barkat Feb 21 '23 at 13:54
  • If you are asking about Python *and* Rust, that's two different questions. Please pick one language for this question. – chepner Feb 21 '23 at 15:00

1 Answers1

1

Why is this happened?

Both will first fully evaluate the RHS, then

in the case of Python, multiple assignment (or "tuple unpacking") is a procedural affair:

[...] The object must be an iterable with the same number of items as there are targets in the target list, and the items are assigned, from left to right, to the corresponding targets.

[...]

If the target is a subscription: The primary expression in the reference is evaluated. It should yield either a mutable sequence object (such as a list) or a mapping object (such as a dictionary). Next, the subscript expression is evaluated.

Note that this evaluation is done on a per-target basis, so with a target of

(l1[a], l1[b], l2[l1[a]], l2[l1[b]])

the assignment will essentially desugar to:

_values = (l1[b], l1[a], a, b)
l1[a] = next(_values)
l1[b] = next(values)
l2[l1[a]] = next(values)
l2[l1[b]] = next(values)

except if you look at the disassembly Python does that more efficiently and eagerly because it uses UNPACK_SEQUENCE rather than advancing the iterable one at a time.

Rust does not clearly define the order of evaluation of destructuring assignment, but you can compile to HIR to see exactly what it desugars to:


fn main() {
        let mut l1 = [0, 1];
        let mut l2 = [0, 1];
        let (a, b) = (0, 1);
        {
                let (lhs, lhs, lhs, lhs) = (l1[b], l1[a], a, b);
                l1[a] = lhs;
                l1[b] = lhs;
                l2[l1[a]] = lhs;
                l2[l1[b]] = lhs;
            };

which turns out to be about the same as Python, and why there is no borrow error. Although because (again) I could not find any specification of the order of evaluation of the assignee expression I'm not sure relying on this behaviour is safe (not that you should leverage it in Python either, it makes for extremely difficult to read code).

Masklinn
  • 34,759
  • 3
  • 38
  • 57
  • In Rust, the order of evaluation is perfectly [defined](https://rust-lang.github.io/rfcs/2909-destructuring-assignment.html#order-of-assignment): first evaluate the RHS, then perform the assignments left-to-right. – Jmb Feb 21 '23 at 16:40
  • 1
    @Jmb it's not well defined, because the spec doesn't define when the evaluation of the places happens. The places could also be evaluated first then assigned to (though that might be constraining wrt borrowing) without changing the order of *assignment*. – Masklinn Feb 21 '23 at 17:13
  • The spec also states: "_A destructuring assignment can always be written as a series of assignments, so this behaviour matches its expansion._" which means that the places can't be evaluated first or the behaviour would no longer match the expansion. – Jmb Feb 22 '23 at 07:24