我有多年的编程经验,但我对 Rust 很陌生。在理解如何在特定场景中使用引用时遇到问题,而编译器却没有告诉我我做错了。
就上下文而言,我正在通过构建一个小型软件合成器来学习 Rust。我使用 egui 作为 UI,使用 rodio 作为音频。
所以,我有一个可以发出声音的振荡器。我将其传递给 rodio 库来播放声音。它通过迭代器(和其他一些东西)来做到这一点,但最重要的部分在底部
return Some(self.get_sample());
pub struct GeneralOscillator {
...
envelope: EnvelopeADSR,
}
impl GeneralOscillator {
pub fn new(...stuff...) -> GeneralOscillator {
let mut oscillator = GeneralOscillator{
...
envelope: EnvelopeADSR::new(),
};
...
return oscillator;
}
fn note_pressed(&mut self){
self.envelope.note_on(time::get_time());
}
pub fn note_released(&mut self){
self.envelope.note_off(time::get_time());
}
pub fn get_amplitude(&self) -> f32 {
return self.envelope.get_amplitude(time::get_time());
}
pub fn get_sample(&mut self) -> f32 {
return self.get_amplitude() * self.note_oscillator.get_sample();
}
}
impl Iterator for GeneralOscillator {
type Item = f32;
fn next(&mut self) -> Option<f32>{
return Some(self.get_sample());
}
}
我需要与该振荡器沟通某个键已被按下或释放。当按下一个音符时,我创建一个振荡器并将其传递给 rodio
Sink
,如下所示:
let osc = GeneralOscillator::new(freq, 44100, wave_table);
self.sink.append(osc);
self.sink.play();
这会导致音符正确播放,并且包络可以正确处理起音/衰减/延音。
我的问题是让振荡器知道我已经释放了按键。从概念上来说,我看到两个选择。我的第一个想法是我想在某处保留对振荡器的引用,以便在释放按键时我可以执行
oscillator.note_released()
。不过,我将振荡器移至 sink.append(osc)
,所以我相信 Rust 不会允许我做类似的事情。
我的第二个想法是我想在振荡器上保留对
something
的引用。每次调用 next()
然后我都可以检查该键是否被释放,类似于(比这更聪明,但概念上有一个对 GeneralOscillator 上其他对象的引用,它可以检查该键是否被释放):
fn next(&mut self) -> Option<f32>{
if self.key_event_handler.key_released() {
self.envelope.note_released();
}
return Some(self.get_sample());
}
我的理解是,由于所有权规则,选项 1 几乎是不可能的。对于 2,我一直遇到生命周期的问题。
我的应用程序有一个带有更新循环的顶级结构。在该应用程序结构上,我可以存储一些了解按键按下/释放状态的信息(为了示例,此处仅称为
KeyChecker
)。我知道这个结构将在程序的整个存在期间存在,因为它包含应用程序循环和所有数据。
struct OxidizerApp {
... stuff ...
key_checker: KeyChecker
}
在更新循环中我尝试执行以下操作
impl App for OxidizerApp {
fn update(&mut self, ... ) {
... stuff
self.start_stop_playing();
}
fn start_stop_playing (&mut self) {
... stuff
let osc = GeneralOscillator::new(freq, 44100, wave_table, &self.key_checker);
self.sink.append(osc);
self.sink.play();
}
}
最后 - 错误:
error[E0521]: borrowed data escapes outside of method
--> src\main.rs:153:13
|
82 | fn start_stop_playing(&mut self){
| ---------
| |
| `self` is a reference that is only valid in the method body
| let's call the lifetime of this reference `'1`
...
153 | self.sink.append(osc);
| ^^^^^^^^^^^^^^^^^^^^^
| |
| `self` escapes the method body here
| argument requires that `'1` must outlive `'static`
我的理解是,由于某种原因,当
self.sink()
取得振荡器的所有权时,它强制其上存在的任何引用都是“静态生命周期”。我知道 &self.key_checker
的生命周期对于所有意图和目的来说都是静态的,但我想编译器无法确定这一点。
对于我在这里做错了什么或误解有什么建议吗?我对此消息的天真解释是,因为更新循环位于
OxidizerApp
上,并且我必须将所有数据存储在该结构上,所以每当我使用像 self
这样对 &self.key_checker
的引用时,它都会抱怨因为self is a reference that is only valid in the method body
错误?
我的主要功能本质上就是这个(以防有什么不同),其中
run_native
等用于 egui UI 框架。
fn main() -> Result<(), eframe::Error> {
let (_stream, stream_handle) = OutputStream::try_default().expect("Failed to create output stream");
let sink = Sink::try_new(&stream_handle).unwrap();
return run_native(
"Test App",
options,
Box::new(|_cc| {
let app = OxidizerApp::default(sink, &WAVE_TABLES);
return Box::<OxidizerApp>::new(app);
}));
}
任何指导或帮助将不胜感激!
我从 Reddit 上的帖子中收到了答案。我正在寻找的是 std::sync::mpsc。
它让我建立一个通道,通过发送器和接收器从主应用程序到振荡器进行通信。