-1

I'm starting to study Rust and I admit, I have some problems with lifetimes and borrowing. On even days I think I got it and on odd days I get bitten ! Too much C or C++ ? Or perhaps too old ? ;-)

The following code does not compile:

use core::f64::consts::PI;

pub struct TableOscillator {
    sample_rate: u32,
    table: &[f64],
}

impl TableOscillator {
    pub fn new(sample_rate: u32, table: &[f64]) -> TableOscillator {
        TableOscillator { sample_rate, table }
    }

    // Other methods ...
}

fn main() {
    const SAMPLE_RATE: u32 = 96000;

    let table: [f64; 1024];
    for i in 0..table.len() {
        let phase = (2.0 * PI * i as f64) / (table.len() as f64);
        table[i] = phase.sin();
    }

    // At this point I would like "table" to be constant and usable by all the following oscillators.

    let osc1 = TableOscillator::new(SAMPLE_RATE, &table);
    let osc2 = TableOscillator::new(SAMPLE_RATE, &table);

    // ...
}

Here is the compiler message:

error[E0106]: missing lifetime specifier
 --> src/main.rs:5:12
  |
5 |     table: &[f64],
  |            ^ expected named lifetime parameter
  |
help: consider introducing a named lifetime parameter
  |
3 ~ pub struct TableOscillator<'a> {
4 |     sample_rate: u32,
5 ~     table: &'a [f64],
  |

A bit of explanation: the different oscillators use their "table" members (only read no write). What is the Rust idiomatic way to fix that ? Thanks !

Herohtar
  • 5,347
  • 4
  • 31
  • 41
ols
  • 173
  • 7
  • What happens if you do what the compiler tells you? – Jmb Jan 28 '22 at 14:10
  • It's definitely worth learning how to apply lifetimes, but if your goal is to get some simple Rust working first, this project would be dramatically simpler if you stored an owned `Vec` rather than a borrowed slice. Unless you plan to have at least hundreds of Oscillators, the cost of copying an extra 8kB is likely not worth the trouble of managing lifetimes (and if it does, consider reaching for `Cow` rather than storing a slice). Some good advice from one of the compiler team members: https://twitter.com/ekuber/status/1476128384908410882 Rust is much simpler if you accept some copying. – Rob Napier Jan 28 '22 at 14:11
  • @Jmb: You are right, but I do a typo when I do what the compiler tells me ! – ols Jan 28 '22 at 14:47
  • 1
    @RobNapier: Thanks for the explanation. I will use lot of oscillators with much larger samples table. Also thanks for the interesting link. – ols Jan 28 '22 at 15:01

1 Answers1

2

In order to do this you need to annotate the lifetime for TableOscillator to tell Rust that the reference needs to live as long as the struct:

pub struct TableOscillator<'a> {
    sample_rate: u32,
    table: &'a [f64],
}

osc1 and osc2 cannot outlive table, because they're borrowing it.

See Validating References with Lifetimes.

lkolbly
  • 1,140
  • 2
  • 6