Rust中结构体字段初始化的顺序是否有保障?

12

我在构造函数 - Rustonomicon中没有找到任何相关的参考资料。以下代码是否保证可行...

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(),
    });
}

...将始终打印以下内容?

[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,
}

编译器是否可以分配不同的值?

请注意,该问题是关于字段初始化的顺序,而不是声明的顺序。

请注意,此问题特别询问结构体,而不是元组,这在Rust中元组的评估顺序是什么? 中有所回答。


1
有一个来自4年前的Reddit帖子:关于执行顺序和结构体初始化器的问题,还有4个相关测试(struct-order-of-eval-1.rsstruct-order-of-eval-2.rs等)。 - Lonami
我真的希望顺序不依赖于在结构体中声明字段的顺序,而仅依赖于初始化字段的顺序...就像你的例子所展示的那样。这将符合函数参数从左到右进行评估的事实。 - Matthieu M.
2
是的,不是它们被声明的顺序,而是在构建实例时指定它们的顺序。 - Lonami
这个回答解决了你的问题吗?Rust中元组的评估顺序是什么? - SCappella
4
我认为这不是一个重复的问题。元组更简单,因为字段的声明顺序和结构体字面量中字段的顺序没有区别。 - mcarton
4个回答

7

是的,它是有保证的。 Ralf Jung,一位编译团队贡献者在 Zulip 上确认:

结构体字段初始化的顺序是否有保证?

RalfJ:

是的——它总是按照你在初始化器中编写字段的顺序进行排序。

结构体定义中字段的顺序是无关紧要的。

screenshot


1
谢谢,但我更希望有一个更正式的声明(例如RFC或在书籍或nomicon中提到)。对我来说,即使是由像Ralf这样重要的人发出的聊天消息,也不如一个适当的官方参考资料有价值。 - Lonami

3

这是一个无关的问题,但您能否请看一下我有关ECMAScript词法语法中多个目标符号的问题?我在esdiscuss档案中发现了这个线程,所以希望您能提供一些见解。 - user51462
我对那个问题的现有答案没有什么可补充的。回想起来,我后悔花了那么多时间为 ES5 的标准化做出贡献。当时看起来很有用,但我本可以做更有用的事情。 - Daira Hopwood

2

是的,因为更改它将会是一项破坏性的更改:

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,
}

我们还可以观察到,实例化时字段的顺序会改变语义值构建的顺序。 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),
    };
}

打印哪个内容

Making 0
Making 1
Making 2

2
你声称“改变它将是一个破坏性的变化”,这是否在书或Nomicon中有记录?(或者曾经有过记录吗?)也许有一个RFC解释这个问题? - Lonami
2
@Lonami,官方已经确定 Rust 不会引入破坏性变更。因此,如果在当前版本中更改被认为是破坏性变更,那么这种更改将不会发生。 - Optimistic Peach
1
@trentcl 不是,我在问是否明确说明了顺序是有保证的,而不是是否允许破坏性更改。 - Lonami
2
@Lonami 如果你不认为“没有破坏性变化”是足够强有力的保证,那么更明确的意义是什么呢?换句话说,如果你不相信Rust开发团队会遵守他们的承诺,以避免一般性的破坏性变化,那么如果他们承诺不进行这个特定的破坏性变化,这会有什么区别呢?我不认为编写文档来列出每一个可能的破坏性变化只是为了说“这些都是我们承诺不做的事情”有任何价值。 - trent
2
无论如何,由于评估顺序似乎没有在任何官方文档中记录,我认为这是目前最好的答案。 - trent
显示剩余7条评论

1

在浏览 Rust 的问题时,我发现了 lurking,它链接到 rust-lang/reference - Document expression evaluation order,其中链接到一个关于 Rust 表达式求值顺序的 IRLO 线程,其中 user fweimer 发布帖子

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.

针对nikomatsakis回答

那段代码是正确的,不可能会有改变。实际上,我更或多或少地认为,在评估顺序方面进行更改的机会已经错过了。

此外,很快就补充道:

更重要的是,在struct 字面量中,字段按照您编写它们的顺序进行评估;如果在构建结构之前发生panic,则以相反的顺序丢弃中间值(一旦完全构建结构,将按照在结构声明中编写它们的顺序丢弃字段,如我记得的那样)。

这增加了我寻找的“官方声明”,类似于Heptamerical的答案。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接