4

I'm following the sine example in the rust-portaudio examples directory (which uses the non-blocking API), and I'm trying to get run() working with a Trait type argument instead of calculating the samples within run() itself.

The trait I've defined is pretty simple:

pub trait Evaluatable {
    fn evaluate(&mut self) -> (f32, f32);
}

I changed the run() function signature to the following to accept my trait:

fn run<E: Evaluatable + 'static>(mut generator: E) -> Result<(), pa::Error>

and upated the callback function to call evaluate() on the trait type instead of generating samples itself:

let callback = move |pa::OutputStreamCallbackArgs { buffer, frames, .. }| {
    let mut idx = 0;
    for _ in 0..frames {
        let samples = generator.evaluate();
        buffer[idx]   = samples.0;
        buffer[idx+1] = samples.1;
        idx += 2;
    }
    pa::Continue
};

E has to be 'static due to this callback (see open_non_blocking_stream()), which is the main source of my frustration...


In main, I can create an Evaluatable type and pass it in just fine (aside: I'm surprised this works, do objects in main get 'static lifetime?):

fn main() {
    // Implements Evaluatable
    // arguments are: sample rate, frequency, amplitude
    let mut sine_generator = SineGenerator::new(44100.0, 200.0, 0.3);

    run(sine_generator).unwrap()
}

But I want to be able to combine signals, so I made a struct that can combine them:

use evaluatable::Evaluatable;

pub struct Combine<'a> {
    first: &'a mut (Evaluatable + 'a),
    second: &'a mut (Evaluatable + 'a),
}

impl<'a> Combine<'a> {
    pub fn new(first: &'a mut Evaluatable, second: &'a mut Evaluatable) -> Combine<'a> {
        Combine {first, second}
    }
}

impl<'a> Evaluatable for Combine<'a> {
    fn evaluate(&mut self) -> (f32, f32) {
        let first_output = self.first.evaluate();
        let second_output = self.second.evaluate();


        (first_output.0 + second_output.0, first_output.1 + second_output.1)
    }
}

And tried to use it in main():

fn main() {
    let mut sine_generator1 = SineGenerator::new(44100.0, 200.0, 0.3);
    let mut sine_generator2 = SineGenerator::new(44100.0, 250.0, 0.3);

    let combine = Combine::new(&mut sine_generator1, &mut sine_generator2);
    run(combine).unwrap()
}

Now, Rust seems to have a problem with the lifetimes not being 'static:

Error[E0597]: `sine_generator1` does not live long enough
  --> src/main.rs:27:37
   |
27 |     let combine = Combine::new(&mut sine_generator1, &mut sine_generator2);
   |                                     ^^^^^^^^^^^^^^^ does not live long enough
28 |     run(combine).unwrap()
29 | }
   | - borrowed value only lives until here
   |
   = note: borrowed value must be valid for the static lifetime...

  1. Why was Rust able to allow me to use SineGenerator from main(), but won't let me use Combine which takes the same objects (with, I assume, the same lifetimes)?
  2. Is there a better way to implement Combine that will allow me to do what I want to do here? I had to take references because Trait types don't have defined size at compile-time.
crs
  • 77
  • 1
  • 8

1 Answers1

2
  1. Why was Rust able to allow me to use SineGenerator from main(), but won't let me use Combine which takes the same objects (with, I assume, the same lifetimes)?

A 'static bound on a type parameter means that the type may not borrow anything that lives shorter than 'static. Your SineGenerator doesn't borrow anything, so it respects that bound. Combine, on the other hand, doesn't respect the bound because it contains borrowed pointers, and you instantiate it with a lifetime that is shorter than 'static by storing references to local variables in the Combine object.

  1. Is there a better way to implement Combine that will allow me to do what I want to do here? I had to take references because Trait types don't have defined size at compile-time.

The typical solution is to use type parameters instead of trait objects.

pub struct Combine<F, S>
where
    F: Evaluatable,
    S: Evaluatable,
{
    first: F,
    second: S,
}

fn main() {
    let sine_generator1 = SineGenerator::new(44100.0, 200.0, 0.3);
    let sine_generator2 = SineGenerator::new(44100.0, 250.0, 0.3);

    let combine = Combine::new(sine_generator1, sine_generator2);
    run(combine).unwrap()
}

By having Combine own the Evaluatables, it will respect the 'static bound (if both F and S are 'static themselves).

Francis Gagné
  • 60,274
  • 7
  • 180
  • 155
  • Worked flawlessly, thanks! Looks like I still have a lot of learning to do with Rust. – crs Sep 17 '17 at 03:28