匹配和解包之间的不同行为

Gab*_*ori 5 error-handling rust

我做了一个小程序,它显示了一种我无法解释的奇怪行为。我正在使用 rodio crate 来尝试一些音频东西。

我已经完成了两个程序,在我看来,它们应该给出相同的结果。

我使用第一个匹配来处理错误:

let sink : Option<Sink> = match rodio::OutputStream::try_default() {
        Ok((_, handle)) => {
            match Sink::try_new(&handle) {
                Ok(s) => Some(s),
                Err(_e) => None,
            }
        },
        Err(_e) => None,
};
println!("{}", sink.unwrap().len());
Run Code Online (Sandbox Code Playgroud)

在上一篇中,我使用了 unwrap 来代替。

let (_, handle) = rodio::OutputStream::try_default().unwrap();
let s = Sink::try_new(&handle).unwrap();
println!("{}",s.len());
Run Code Online (Sandbox Code Playgroud)

第一个按预期执行打印语句,而最后一个在第二次解包时出现恐慌。

一旦没有错误传播、隐式转换或其他可以解释这一点的东西,这对我来说就很奇怪。这里的问题与错误本身无关,而是与两个代码之间的差异有关。

Mas*_*inn 8

问题是rodio 的范围界定和实现细节之一:这里的一个关键项目是OutputStream::try_default(),你如何处理它并不重要Sink::try_new(&handle),它的行为总是相同的,而不是这样try_default,如果你匹配或者if let它会工作得很好,如果你unwrap这样做它就会失败。

但为什么会这样呢,两者应该是等价的。答案就在 rodio 的细节中,特别是OutputStreamHandle

pub struct OutputStreamHandle {
    mixer: Weak<DynamicMixerController<f32>>,
}
Run Code Online (Sandbox Code Playgroud)

因此,OutputStreamHandle(此后的 OSH)仅保留对 a 的弱引用DynamicMixerControllerOutputStream(此后的 OS)对其持有强引用。

这意味着只有操作系统处于活动状态,OSH 才能“工作”。

let (_, handle) = OutputStream::try_default().unwrap();
Run Code Online (Sandbox Code Playgroud)

没有命名操作系统,因此不保留它,它立即被丢弃,被Arc释放,OSH 不保留任何内容,并且接收器不高兴。

那么另外一个如何工作呢?因为

    if let Ok((_, handle)) = OutputStream::try_default() {
        let sink = Sink::try_new(&handle).unwrap();
        println!("match {}", sink.len());
    }
Run Code Online (Sandbox Code Playgroud)

确实是

{
    let _var = OutputStream::try_default();
    if let Ok((_, handle)) = _var {
        let sink = Sink::try_new(&handle).unwrap();
        println!("match {}", sink.len());
    }
}
Run Code Online (Sandbox Code Playgroud)

所以match/if let本身正在保持Result活力,这在这里是一件幸事(但会在下一个问题中引起问题)。

由于Result保持活动,元组保持活动,OutputStream保持活动,Arc保持活动,因此 OSH 具有工作mixer,接收器可以使用它。

OutputStream您可以通过将 绑定到“正确的”名称来解决第二个版本的问题,例如

let (_stream, handle) = OutputStream::try_default().unwrap();
Run Code Online (Sandbox Code Playgroud)

为名称添加前缀_将创建真正的绑定,但会抑制“未使用的变量”警告。

FWIW 小心处理这类事情对于 RAII 类型非常重要,这些类型的值是您“不需要”的,最常见的是互斥体保护代码而不是数据:

let _ = m.lock().unwrap();
Run Code Online (Sandbox Code Playgroud)

不执行任何操作,互斥量防护不会保持活动状态,因此锁会立即释放。在这种情况下,你宁愿

let _lock = m.lock().unwrap():
Run Code Online (Sandbox Code Playgroud)

甚至更好

let lock = m.lock().unwrap();
...
drop(lock);
Run Code Online (Sandbox Code Playgroud)