Lonami
Lonami

Reputation: 7096

Is the order in which struct fields are initialized guaranteed in Rust?

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?.

Upvotes: 15

Views: 2046

Answers (4)

Daira-Emma Hopwood
Daira-Emma Hopwood

Reputation: 2359

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.

Upvotes: 3

Lonami
Lonami

Reputation: 7096

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.

Upvotes: 1

Heptametrical
Heptametrical

Reputation: 106

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

Upvotes: 9

Optimistic Peach
Optimistic Peach

Reputation: 4288

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

Upvotes: 2

Related Questions