0

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.

poidl
  • 433
  • 1
  • 4
  • 9
  • 1
    This question would be much clearer if it concentrated on *one* use case and e.g. described a simple example of interfacing C and Rust possible to follow without specialized knowledge. It should probably also be split - one question is about references vs. pointers in `AmpNew`, and another about exposing Rust's generic functions to C. – user4815162342 Dec 03 '16 at 10:22
  • I'm providing a specific use case in the example. Isn't the plugin as simple as it gets? The floats in the host-provided input buffer are multiplied by a constant. I agree that there are multiple questions, hence one is marked as the "main question". – poidl Dec 03 '16 at 10:51
  • 1
    I meant a use case that doesn't require external code and concepts to understand. For example, "take a C function with the following signature and the following ownership semantics; how would one go about wrapping it in Rust?" – user4815162342 Dec 03 '16 at 11:30
  • OK fair enough I'll simplify it tomorrow. The drawback is it won't be complete any more. My previous questions all got edited by rust gurus anyways, let's see tomorrow. – poidl Dec 03 '16 at 12:03
  • 1
    Maybe it can be made complete (and self-contained) by providing the sample (and trivial) implementation of the C part? Ideally the question would include an [SSCCE](http://sscce.org/), but that's not always feasible in practice. – user4815162342 Dec 03 '16 at 12:32
  • OK I'll try to write SSCCE, what I meant by "complete" was "real life", i.e. a true LV2 plugin. But yeah I get it, for most people here that's little value if they're not into audio. I just thought, given it's so simple, I could get away with it. – poidl Dec 03 '16 at 13:51
  • Good point, but you can still include a github (or pastebin etc.) link for those who prefer to see the real-world use case. – user4815162342 Dec 03 '16 at 13:59
  • 2
    Also, that GitHub user **might be wrong or incomplete**. There should be a `lv2-sys` (or something reasonable) that creates a [\*-sys package](http://doc.crates.io/build-script.html#-sys-packages). That crate **only** exposes the raw C API. Then, it's up to *other* crates (potentially more than one!) to expose an idiomatic Rust API. Maybe your crate should really just be the \*-sys package. – Shepmaster Dec 03 '16 at 16:07
  • Interesting, I did not know about *-sys packages. I think that was the hint I was looking for. So one packs all the close-to-C-like stuff in there, and provides a separate more high-level ("opinionated") package building on that? Sounds good. – poidl Dec 04 '16 at 02:12
  • All that opinionated and idiomatic API is closer to what @user4815162342 was getting at. If you phrase your question in terms of what the ownership and lifetimes of the underlying C code are, then we will be able to provide useful guidance on how to make a better API on top of it. – Shepmaster Dec 04 '16 at 05:14
  • Good idea, I'll do that but first I think I'll improve the low level API. That will be more useful for people who know how to design the high level API. – poidl Dec 04 '16 at 09:38

0 Answers0