Enums Rust中逻辑上但非法的未初始化变量

Enums Rust中逻辑上但非法的未初始化变量,enums,rust,refactoring,Enums,Rust,Refactoring,我对生锈还比较陌生。这个问题很长,所以我将从底线开始: 您喜欢哪种解决方案?你有什么想法或意见吗 我的代码没有编译,因为第6行(prev=curr)和第12行(bar(…))使用了编译器怀疑未初始化的变量。作为程序员,我知道没有理由担心,因为第6行和第12行不会在第一次迭代期间运行 let mut curr: Enum; let mut prev: Enum; for i in 0..10 { if i > 0 { prev = curr; }

我对生锈还比较陌生。这个问题很长,所以我将从底线开始: 您喜欢哪种解决方案?你有什么想法或意见吗

我的代码没有编译,因为第6行(
prev=curr
)和第12行(
bar(…)
)使用了编译器怀疑未初始化的变量。作为程序员,我知道没有理由担心,因为第6行和第12行不会在第一次迭代期间运行

let mut curr: Enum;
let mut prev: Enum;

for i in 0..10 {
    if i > 0 {
        prev = curr;
    }

    curr = foo();

    if i > 0 {
        bar(&curr, &prev);
    }
}
我知道你从编译器那里所能得到的知识是有限的。所以我想出了三种不同的方法来回答语言的安全限制

1)初始化并停止过多思考

我可以用任意值初始化变量。风险在于,维护人员可能会错误地认为这些初始值(希望未使用)具有重要意义。第1-2行将变成:

let mut curr: Enum = Enum::RED; // Just an arbitrary value!
let mut prev: Enum = Enum::BLUE; // Just an arbitrary value!
2)将
None
值添加到
Enum

第1-2行将成为

let mut curr: Enum = Enum::None;
let mut prev: Enum = Enum::None;
let mut curr: Option<Enum> = None;
let mut prev: Option<Enum> = None;
prev = match curr {
    Some(_) => curr,
    None => panic!("Ah!")
};
我不喜欢这个解决方案的原因是,现在我在枚举中添加了一个新的可能的非真实值。为了我自己的安全,我必须添加断言检查并将分支匹配到
foo()
bar()
,以及使用
Enum
的任何其他函数

3)
选项

我认为这个解决方案是最“按部就班”的解决方案,但它使代码变得更长,更难理解

第1-2行将成为

let mut curr: Enum = Enum::None;
let mut prev: Enum = Enum::None;
let mut curr: Option<Enum> = None;
let mut prev: Option<Enum> = None;
prev = match curr {
    Some(_) => curr,
    None => panic!("Ah!")
};
而对
bar()
的幼稚调用会让人讨厌

match prev {
    Some(_) => bar(&curr, &prev),
    None => panic!("Oy!")
};
问题:您喜欢哪种解决方案?你有什么想法或意见吗?

我对生锈也很陌生,但我觉得第三种选择最清楚。我要补充的是,可以使用
unwrap()
expect()
来代替具有相同含义的匹配表达式(“我知道这已被占用”):


请参阅。

在可能的情况下,避免在Rust中使用太多可变变量绑定,尤其是在循环中。使用变异变量管理循环中的状态会使代码更难理解并导致错误。在
for
循环中看到一个计数器变量通常是一个危险信号,它通常可以替换为对值的迭代器

在您的情况下,您可以对
foo
生成的值创建一个迭代器,并使用板条箱中的
tuple\u窗口
将它们成对地传递到
bar

请注意,您不能在这里犯错误,而忘记设置
prev
curr
,未来的维护人员也不能意外地交换两行代码并将其弄糟

这种风格非常简洁和富有表现力。例如,我在上面编写代码片段的方式强调,
bar
将被调用9次。如果您更愿意强调
foo
将被调用10次,您也可以对其稍加修改:

for (prev, curr) in repeat_with(foo).take(10).tuple_windows() {
    bar(&curr, &prev);
}

实际上,你所表达的逻辑就是所谓的“折叠”,你可以使用折叠而不是循环

我们可以做的是:

(0..9).map(|_|foo()) //an iterator that outputs foo() 9 times, not 10
   .fold(foo(), |prev, curr| {bar(&curr, &prev); curr});
业务端当然是带有
.fold
的行。fold所做的是,它使用一个参数作为初始值,对该初始值和迭代器生成的当前值调用一个二进制函数,然后将结果用作下一次迭代的新初始值。我只是在这里使用了一个函数,它与您一样被调用以产生副作用,并且作为结果值使用
curr
值,它因此被用作下一次迭代的初始值,并用作
prev

此表达式返回的最终值是最后一个
curr
,可以忽略该值


无论如何,修复代码的一个简单方法是将第一次迭代从循环中拉出来。由于使用了
if
语句,第一次迭代只需调用
foo()
。此外,您还可以将分配移动到循环的末尾的
prev
,以便将作用域
curr
移动到循环的内部:

let mut prev = foo();
for _ in 1..10 {
    let curr = foo();
    bar(&curr, &prev);
    prev = curr;
}
注意,我将范围更改为从1开始,而不是从0开始,因为我们将第一次迭代从循环中拉出


我不认为这种方法比公认答案中的方法更好,但我确实认为它非常简单易懂。

谢谢你的回答。您的解决方案会导致
foo()
超出需要运行一次。我很高兴这不是我的问题。我仍然希望在第一次迭代期间跳过运行
bar()
。你会怎么做呢?@JonathanJacobson在你的原始代码中,以及在这个答案中建议的两个版本中,
foo
被调用10次,
bar
被调用9次。你的意思是想让
bar
只被调用8次吗?@JonathanJacobson此代码的迭代次数比原始代码少一次,这意味着你问题中的
if i>0
部分内置在
元组窗口
适配器中。你是说你问题中的代码不正确,它实际上应该说
如果i>1
?@JonathanJacobson在上面的代码中,
foo
bar
第一次运行之前运行两次。它必须这样做,否则就不会有两个值可用来调用
bar
。此程序的输出与您问题中代码的输出相同。