11

I could not find any reference to this in Constructors - The Rustonomicon. Is it guaranteed that the following code…

struct Mutates {
    n: usize,
}

impl Mutates {
    fn side_effects(&mut self) -> usize {
        self.n += 1;
        self.n
    }
}

#[derive(Debug)]
struct Struct {
    a: usize,
    b: usize,
}

fn main() {
    let mut m = Mutates { n: 0 };

    // note the order of the fields
    dbg!(Struct {
        a: m.side_effects(),
        b: m.side_effects(),
    });
    dbg!(Struct {
        b: m.side_effects(),
        a: m.side_effects(),
    });
}

…will always print the following?

[src/main.rs:22] Struct{a: m.side_effects(), b: m.side_effects(),} = Struct {
    a: 1,
    b: 2,
}
[src/main.rs:26] Struct{b: m.side_effects(), a: m.side_effects(),} = Struct {
    a: 4,
    b: 3,
}

Or is it possible for the compiler to assign different values?

Note that the question is about the order in which fields are initialized, not declared.

Note that this question is specifically asking about structs and not tuples, which is answered by What is the evaluation order of tuples in Rust?.

Lonami
  • 5,945
  • 2
  • 20
  • 38
  • 1
    There's a reddit thread from 4 years ago: [Question about execution order and struct initializers](https://www.reddit.com/r/rust/comments/3vgvs2/question_about_execution_order_and_struct/), and also 4 tests on this ([`struct-order-of-eval-1.rs`](https://github.com/rust-lang/rust/blob/master/src/test/ui/structs-enums/struct-order-of-eval-1.rs), [`struct-order-of-eval-2.rs`](https://github.com/rust-lang/rust/blob/master/src/test/ui/structs-enums/struct-order-of-eval-2.rs), etc.) – Lonami May 24 '20 at 10:58
  • I would really hope that the order is NOT dependent on the order in which the fields were declared in the struct, and solely dependent on the order in which the fields are initialized... just like your example shows. It would be in line with the fact that functions arguments are evaluated left-to-right. – Matthieu M. May 24 '20 at 11:05
  • 2
    Yes, not the order in which they are declared, but the order in which you specify them when constructing an instance of it. – Lonami May 24 '20 at 11:22
  • Does this answer your question? [What is the evaluation order of tuples in Rust?](https://stackoverflow.com/questions/54313350/what-is-the-evaluation-order-of-tuples-in-rust) – SCappella May 24 '20 at 11:37
  • 4
    @SCappella I would say that this is not a duplicate. Tuples are simpler in that there is no difference between the declaration order of fields and the order of the fields in a struct literals. – mcarton May 24 '20 at 12:09

4 Answers4

7

Yes, it's guaranteed. Ralf Jung, a compiler team contributor confirms it on Zulip:

Is the order in which struct fields are initialized guaranteed?

RalfJ:

yes -- it's always the order in which you write the fields in the initializer

the order of the fields in the struct definition is irrelevant

screenshot

Community
  • 1
  • 1
  • 1
    Thanks, but I would prefer if there was a more official statement about it (for example, an RFC or have it mentioned somewhere in the book or nomicon). To me a chat message, even if done by someone as important as Ralf is not as valuable as a proper, official reference. – Lonami May 25 '20 at 11:24
3

It's documented in The Rust Reference here that:

Expressions taking multiple operands are evaluated left to right as written in the source code.

This explicitly includes struct expressions. The documentation PR was https://github.com/rust-lang/reference/pull/888, which closed issue https://github.com/rust-lang/reference/issues/248 mentioned in Lonami's answer.

Daira Hopwood
  • 2,264
  • 22
  • 14
  • this is an unrelated question but could you please take a look at my [question](https://stackoverflow.com/q/69983078/10841085) regarding multiple goal symbols in the ECMAScript lexical grammar? I came across this [thread](https://www.mail-archive.com/es-discuss@mozilla.org/msg02194.html) in the esdiscuss archives, so was hoping you would have some insight. – user51462 Nov 16 '21 at 02:33
  • I don't really have anything to add to the existing answers for that question. With hindsight, I regret all the time that I put into contributing to standardization of ES5. It seemed useful at the time, but I could have been doing things that were much more useful. – Daira Hopwood Dec 03 '21 at 19:48
2

Yes, because changing it would be a breaking change:

struct Foo(usize);

impl Foo {
    fn make_val(&mut self) -> usize {
        self.0 + 20
    }
}

struct Bar {
    a: Foo,
    b: usize,
}

let mut foo = Foo(10); // Not copy or clone.
// let bar = Bar {        //Wouldn't work since `foo` is moved into a.
//     a: foo,
//     b: foo.make_val(),
// };

let bar = Bar {
    b: foo.make_val(),
    a: foo,
}

And we can also observe that order of fields on the instantiation side changes the order in which the values are semantically built. Playground.

#![allow(dead_code)]
struct Bar;
impl Bar {
    pub fn new(val: usize) -> Self {
        println!("Making {}", val);
        Bar
    }
}

struct Foo {
    a: Bar,
    b: Bar,
    c: Bar,
}

fn main() {
    Foo {
        a: Bar::new(0),
        c: Bar::new(1),
        b: Bar::new(2),
    };
}

Which prints

Making 0
Making 1
Making 2
trent
  • 25,033
  • 7
  • 51
  • 90
Optimistic Peach
  • 3,862
  • 2
  • 18
  • 29
  • 2
    You claim "changing it would be a breaking change", is this documented anywhere in the book or nomicon? (Or was it documented in the past?) Maybe there's a RFC explaining this? – Lonami May 25 '20 at 11:25
  • @Lonami The code written demonstrates the breaking-ness of the change. Are you asking where it is documented that language changes are guaranteed to be non-breaking? – trent May 25 '20 at 12:30
  • 2
    @Lonami What is official is that Rust will not introduce breaking changes. So if changing this would be considered a breaking change in the current edition, then it won't happen. – Optimistic Peach May 25 '20 at 12:46
  • 1
    @trentcl no, I am asking if it's explicitly stated that the order is guaranteed, not if breaking changes are allowed. – Lonami May 25 '20 at 13:13
  • @OptimisticPeach yes, but it could also be considered an implementation detail which even though they don't normally change if it has a large effect (would break many crates), it still could technically change, hence why I was asking if the behaviour is documented anywhere. – Lonami May 25 '20 at 13:14
  • @Lonami I'm not sure I understand. Even if someone considers it an "implementation detail", it's still demonstrably a breaking change. The code in this answer proves it, don't you agree? – trent May 25 '20 at 13:16
  • (To be fair, there are "breaking-ish" changes that are allowed where code that previously was unambiguous becomes ambiguous and fails to compile due to type inference, but that's not the same as a change where previously working code changes its meaning. The second thing is the kind of "breaking change" that Rust officially guarantees to avoid.) – trent May 25 '20 at 13:21
  • Enforcing the use of `dyn` is a breaking change that's made over an edition gap. Its lack is currently deprecated and linted against, and `dyn` is being phased in. If they do change it, it would be over the course of many months and would have many big red signs telling you that it's going to change, and when it does change, it will in a new edition. But it's not going to change because this is pretty important to the language. – Optimistic Peach May 25 '20 at 16:08
  • 1
    @trentcl my point is that if it's not defined anywhere people may be relying on it when they shouldn't have been, and I want to see where it's defined. I can see it would be a breaking change, but I can't see where is documented, other than being an implementation detail. Heptametrical's answer for example quotes a compiler team contributor rather than just claiming code would break. – Lonami May 25 '20 at 16:08
  • 2
    @Lonami If you don't consider "no breaking changes" a strong enough guarantee, then what would be the point of being more explicit? In other words, if you don't trust the Rust dev team to stick to their promise to avoid breaking changes in general, what difference would it make if they *also* promise not to make *this particular* breaking change? I don't see any value in writing documentation to list every possible breaking change just to say "these are all the things we promise not to do." – trent May 25 '20 at 21:29
  • 2
    In any case, since the evaluation order does not seem to be officially documented anywhere, I think this is the best possible answer at this time. – trent May 25 '20 at 21:33
  • 1
    I'll accept Hepta's answer because they quote a member of the compiler team, while this one just claims it would be a breaking change (which is also shown by the question itself, just a different form of side-effect), but thanks for the debate. – Lonami May 26 '20 at 09:56
1

After lurking through Rust's issues, I came across rust-lang/reference - Document expression evaluation order, which links to an IRLO thread on Rust expression order of evaluation where user fweimer posts:

What’s the current state regarding order of evaluation? It is very tempting to write this:

struct Item {
    a: u32,
    b: u32,
}

impl Item {
    fn receive_word(&mut self) -> Result<u32, Error> {
        …
    }

    fn receive(&mut self) -> Result<Item, Error> {
        Ok(Item {
            a: self.receive_word()?,
            b: self.receive_word()?,
        })
    }
}

The expectation is that first the value a is received, then the value b. But with a non-determinate evaluation order, one has to introduce temporaries.

To which nikomatsakis responds:

That code is correct and there is no chance it will change. In fact, I’m more or less of the opinion that the ship has sailed with respect to making changes to order of evaluation, period.

And soon after adds:

More to the point, in a struct literal, the fields are evaluated in the order you write them; if a panic occurs before the struct is completely build, the intermediate values are dropped in the reverse order (once the struct is fully built, the fields are dropped in the order they are written in the struct declaration, iirc).

Which adds to the "official statement" I was looking for, similar to Heptamerical's answer.

Lonami
  • 5,945
  • 2
  • 20
  • 38