Description
As of now, all port types return &'static
references to their contents, which allows them to be stored outside of the run()
function, at which point the host could invalidate them (the following code compiles fine today):
struct Amp {
foo: Option<&'static [f32]>
}
struct Ports { input: InputPort<Audio> }
impl Plugin for Amp {
fn run(&mut self, ports: &mut Ports, _features: &mut (), _: u32) {
self.foo.replace(&*ports.input); // Uh-oh
}
}
While this example is rather obvious in that it's doing something very odd, issues can be viciously subtle with more complex port types such as Atom
: one could want to store a deserialized value.
I haven't made any in-depth testing, but I see two solutions to this:
The first would be to make PortType
generic over a 'a
lifetime:
pub trait PortType<'a> {
type InputPortType: Sized + 'a;
type OutputPortType: Sized + 'a;
}
impl<'a> PortType<'a> for Audio<'a> {
type InputPortType = &'a [f32];
type OutputPortType = &'a mut [f32];
}
However, this has a cascading effect, which would force all downstream users of port types to specify their lifetimes:
#[derive(PortCollection)]
struct Ports<'a> {
gain: InputPort<'a, Control<'a>>,
input: InputPort<'a, Audio<'a>>,
output: OutputPort<'a, Audio<'a>>,
}
The second option would be to make the associated types themselves generic:
pub trait PortType {
type InputPortType<'a>: Sized;
type OutputPortType<'a>: Sized;
}
impl PortType for Audio {
type InputPortType<'a> = &'a [f32];
type OutputPortType<'a> = &'a mut [f32];
}
However, this would require Generic Associated Types being stabilized, but while it seems to be making steady progress, there doesn't seem to be any deadline coming soon.
Both options are breaking changes however.
I think we could potentially use the first solution now, and move to GATs when they are stabilized, making two breaking changes.