The issue is one of scoping and an implementation detail of rodio: the one critical item here is OutputStream::try_default(), it doesn't really matter how you handle Sink::try_new(&handle) it'll always behave the same, not so try_default, if you match or if let it it'll work fine, if you unwrap it it'll fail.
But why would that be, the two should be equivalent. The answer is in the details of rodio, specifically of OutputStreamHandle:
pub struct OutputStreamHandle {
mixer: Weak<DynamicMixerController<f32>>,
}
So an OutputStreamHandle (OSH thereafter) only holds a weakref to a DynamicMixerController, to which the OutputStream (OS thereafter) holds a strong reference.
This means the OSH only "works" as long as the OS is alive.
let (_, handle) = OutputStream::try_default().unwrap();
does not name the OS, so doesn't hold onto it, it's immediately dropped, the Arc is released and the OSH holds onto nothing, and the sink is unhappy.
How can the other one work then? Because
if let Ok((_, handle)) = OutputStream::try_default() {
let sink = Sink::try_new(&handle).unwrap();
println!("match {}", sink.len());
}
is really rather
{
let _var = OutputStream::try_default();
if let Ok((_, handle)) = _var {
let sink = Sink::try_new(&handle).unwrap();
println!("match {}", sink.len());
}
}
so the match/if let itself is keeping the Result alive, which is a blessing here (but causes issues just the next question over).
Since the Result is kept alive, the tuple is kept alive, the OutputStream is kept alive, the Arc is kept alive, and thus the OSH has a working mixer, which the sink can make use of.
You can fix the issue of the second version by binding the OutputStream to a "proper" name e.g.
let (_stream, handle) = OutputStream::try_default().unwrap();
prefixing a name with _ will create the binding for real, but will suppress the "unused variable" warning.
FWIW being careful around this sort of things is incredibly important around RAII types whose values you "don't need", most commonly mutexes protecting code as opposed to data:
let _ = m.lock().unwrap();
doesn't do anything, the mutex guard is not kept alive, so the lock is immediately released. In that case, you'd rather
let _lock = m.lock().unwrap():
or even better
let lock = m.lock().unwrap();
...
drop(lock);