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);