I'm a beginner writing an LV2 audio plugin in Rust, and as a "side effect" I wrote an LV2 interface in Rust. A GitHub user noted that
this is a raw C API that you can only use in unsafe. A good library needs a rustic api with traits and types.
and I have questions on how to do that.
EDIT The question was addressed by Shepmaster's comment below. Although the comment doesn't answer the question directly, it makes the question much less relevant to me at the moment. I wrongly assumed that all Rust libraries should provide an idiomatic Rust interface.
Consider a very simple amplifier plugin in C, which I translated to Rust like so:
extern crate libc;
extern crate lv2;
use std::ptr;
// This is a simple "amplifier" plugin. It holds references to input and output
// buffers, and a reference to a single number (gain) by which the input buffer
// is multiplied. Memory managemet of these resources is done by the host.
pub struct AmpNew<'a> {
gain: &'a f32,
input: &'a [f32],
output: &'a mut [f32],
}
// The main question is: Should one use raw pointers instead of references in this
// struct?
// I'd say NO, because:
// I want to implement "lv2::LV2HandleNew"
// without having to use "unsafe", i.e. to "provide a safe interface" to the
// lv2 library. What is the correct (let's say idiomatic) way to "provide a safe
// interface"?
// I'd say YES, because:
// *) AFAIK it's impossible to implement a constructor "::new()" function for this
// struct, since the resources go out of scope when "new()" terminates. Instead, the
// plugin is instantiated by the lv2::instantiate::<>() function using
// libc::malloc(), similar to the code I commented at the bottom of this page.
// It seems weired to not being able to allocate the plugin from safe code.
// How would one design a plugin interface in pure Rust (i.e.
// with a host written in Rust)?
// *) There are potential problems arising with unkown buffer sizes. The function
// connect_port() below takes an "&'a mut [f32]", which is constructed in the
// calling function (containing unsafe code) by "slice::from_raw_parts_mut()",
// which needs a buffer size as argument. This size is not passed by host to
// the extern "C" connect_port(). Instead,
// the host passes the "n_samples" argument to the real-time "run()" function to
// indicate which part of the buffer it wants to have processed by the plugin.
// This may not be an issue, since one can pass a really high buffer size
// to "slice::from_raw_parts_mut()" without having to worry about perfomance/space,
// since no resources are actually allocated anyways, right?
impl<'a> lv2::LV2HandleNew<'a> for AmpNew<'a> {
// For now, initialize() is a placeholder function that doesn't do anything. More complicated plugins may scan host features, set a sample rate, etc.
fn initialize(&mut self) {}
fn connect_port(&mut self, port: u32, data: &'a mut [f32]) {
match port {
0 => self.gain = &data[0] as &f32, // data may be NULL pointer, so don't dereference!
1 => self.input = data as &'a [f32],
2 => self.output = data,
_ => panic!("Not a valid PortIndex: {}", port),
}
}
fn activate(&mut self) {}
fn run(&mut self, n_samples: u32) {
let coef: f32;
match *self.gain > -90.0 {
true => coef = (10.0 as f32).powf(self.gain * 0.05),
false => coef = 0.0,
}
for x in 0..n_samples {
let i = x as usize;
self.output[i] = self.input[i] * coef;
}
}
fn deactivate(&mut self) {}
fn cleanup(&mut self) {}
}
type Newtype<'a> = AmpNew<'a>;
// If I understand correctly, the lv2::LV2Descriptor struct that is delivered
// to the host by "lv2_descriptor()" CANNOT be generic over "Newtype", since this
// would require "lv2_descriptor()" to be generic. But functions called from C
// (by their name) CANNOT be generic.
// The reason why "lv2::instantiate::<>" etc. CAN be generic, is that those functions
// get passed to C via FUNCTION POINTERS contained in a #[repr(C)] struct.
// A secondary question is: Is this necessary? How to implement this more
// effectively?
static S: &'static [u8] = b"http://example.org/eg-amp_rust\0";
static mut desc: lv2::LV2Descriptor = lv2::LV2Descriptor {
uri: 0 as *const libc::c_char, // ptr::null() isn't const fn (yet)
instantiate: lv2::instantiate::<Newtype>,
connect_port: lv2::connect_port::<Newtype>,
activate: lv2::activate,
run: lv2::run::<Newtype>,
deactivate: lv2::deactivate,
cleanup: lv2::cleanup,
extension_data: lv2::extension_data,
};
#[no_mangle]
pub extern "C" fn lv2_descriptor(index: i32) -> *const lv2::LV2Descriptor {
if index != 0 {
return ptr::null();
} else {
// credits to ker on stackoverflow: http://stackoverflow.com/questions/31334356/static-struct-with-c-strings-for-lv2-plugin (duplicate) or http://stackoverflow.com/questions/25880043/creating-a-static-c-struct-containing-strings
let ptr = S.as_ptr() as *const libc::c_char;
unsafe {
desc.uri = ptr;
return &desc as *const lv2::LV2Descriptor;
}
}
}
// fn instantiate<T>() -> *mut libc::c_void {
// let ptr: *mut libc::c_void;
// unsafe {
// ptr = libc::malloc(mem::size_of::<T>() as libc::size_t) as *mut libc::c_void;
// let plgptr = ptr as *mut T;
// }
// ptr
// }
// impl<'a> AmpNew<'a> {
// fn new_ptr() -> *mut libc::c_void {
// instantiate::<AmpNew>()
// }
// }
Specific questions are contained in the code. The code uses functions and types defined here.