Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What are "sequence point"/"sequenced-before" rules in Rust?

Tags:

c++

rust

What are the rules in Rust, analogous to the rules, described here http://en.cppreference.com/w/cpp/language/eval_order for C++?

For now I found empirically, that
1) Arguments of functions are evaluated in direct order
2) All built-in operations with side effects (=, +=, -=, etc.) return unit, so it is hard (but possible) to compose expressions, which would show UB in C++.
One example:

let mut a = 1i;
let b = 2i;
let c = 3i;
let d = (a = b) == (a = c); // What is a? (a is actually 3)

3) It seems like function calls are sequenced as in C++
4) It seems like built-in operations are sequenced as if they were function (method) calls, i.e. order of evaluation is tied to the operator precedence

Are my conclusions correct? What is the exact evaluation model?

like image 441
user2665887 Avatar asked Jul 01 '14 11:07

user2665887


People also ask

What is sequence points?

A sequence point defines any point in a computer program's execution at which it is guaranteed that all side effects of previous evaluations will have been performed, and no side effects from subsequent evaluations have yet been performed.

Is comma a sequence point?

A comma can only occur between two expressions – commas separate expressions – unlike the semicolon, which occurs at the end of a (non-block) statement – semicolons terminate statements. The comma operator has the lowest precedence of any C operator, and acts as a sequence point.

What is sequence point in C++?

Pre-C++11 Definitions A sequence point is a point in the execution sequence where all side effects from the previous evaluations in the sequence are complete, and no side effects of the subsequent evaluations started.


1 Answers

I don't believe it has been explicitly defined, my grepping of the manual didn't turn up anything. However, I can guarantee that these things will not be undefined behaviour (Rust explicitly avoids UB outside unsafe code), and I would be surprised if it were anything but "left-to-right", that is, the order you have deduced. Although, the resolution to #6268 may result in methods evaluating the receiver last (or maybe not, this is just one possibility).

I opened #15300.


Btw, if you are investigating this, you can make the compiler split out pretty control flow graphs with the precise order of evaluation (NB. this is all internal APIs, and so cannot be relied on, is more likely than normal to make the compiler crash, and doesn't hide the compiler implementation details: it's mainly designed for people working on rustc).

As @pnkfelix points out, the compiler isn't using the CFG for code generation (as of 2014-07-02), meaning the CFG isn't guaranteed to be exactly accurate.

E.g. taking a stripped down version of one of @ChrisMorgan's examples:

fn foo(_: (), x: int) -> int {
    x
}

fn main() {
    let mut a = 1;
    { // A
        a * foo(a = 3, a)
    };
}

We want the compiler to split out a control flow graph of the statements/expressions within some block (i.e. { ... }), which can be done via the --pretty flowgraph=<nodeid> option to the compiler, but for that we need to have the ID of our block of interest. In this case, the block we want is A. To get the id compile with rustc --pretty expanded,identified (note that just using identified is a pointless relic: the ids now only get assigned after macro expansion):

#![feature(phase)]
#![no_std]
#![feature(globs)]
#[phase(plugin, link)]
extern crate std = "std#0.11.0-pre";
extern crate native = "native#0.11.0-pre";
use std::prelude::*;
fn foo(_ /* pat 7 */: (), x /* pat 11 */: int) -> int { (x /* 15 */) } /*
block 14 */ /* 4 */

fn main() {
    let mut a /* pat 22 */ = (1 /* 23 */);
    ({
         ((a /* 28 */) *
             ((foo /* 30
                  */)(((a /* 32 */) = (3 /* 33 */) /* 31 */), (a /* 34 */)) /*
                 29 */) /* 27 */)
     } /* block 26 */ /* 25 */);
} /* block 18 */ /* 16 */

Lots of icky internals junk, but the thing we need is there in a comment /* block 26 */. rustc --pretty flowgraph=26 gives a dot file, which renders to the following. You can consult the identifier annotated source above to work exactly what each expression is (the id = .. bit):

CFG

(sorry for length, obviously this code had no branching and so is just a long chain of operations.)

FWIW, that expression evaluates to 9, while I was expecting 3 (and the control flow graph confirms that the isolated a on the LHS is being evaluated after the RHS, including the a = 3 there). I've raised this on #15300 (e: and now isolated it to a strange difference in evaluation order).

like image 181
huon Avatar answered Oct 13 '22 10:10

huon