1

I've been recently learning Rust while trying to make a Synthesia-like app (similar to a piano roll). While separating some of the code into its own module, I got into trouble trying to write a "constructor" method that can hold onto some data. Here is a minimal example:

use std::fs;
use midly::Smf; // MIDI parsing library

struct MidiFile<'a> {
    bytes: Vec<u8>,
    data:  Smf<'a>, // these are the parsed bytes
    ... // more members, ignored for simplicity
}

impl<'a> MidiFile<'a> {
    pub fn from_file(filepath: &'static str) -> Self {
        let bytes = fs::read(filepath).unwrap();
        let data = Smf::parse(&bytes).unwrap();
        Self {bytes, data} // here's the problem
    }
}

Basically all the lifetimes in the code have been asked by the compiler. Although I understand the basic idea of lifetimes, I've yet to understand its syntax and inner workings.

I think it should be possible to create a struct that holds members "A" and "B" (which holds a reference to "A") such that the struct and its members share the same lifespan, but I haven't been able to figure out how to do it correctly, or if it's just a flawed idea to begin with.

These are the error messages:

> returning this value requires that `bytes` is borrowed for `'a`
> cannot move out of `bytes` because it is borrowed (by "data")
> cannot return value referencing local variable `bytes`

I do understand the error messages and the problems with the code as it stands, but I haven't been able to find a workaround, so I could use some help here.

I've tried getting the "byte" into a Box on the heap, such that when "data" borrows the bytes, it actually borrows the data on the heap (which should stay alive until the pointer is dropped) thus allowing the Box to be moved into the struct freely... or that's what I expected. The error messages didn't change, so the problem remains the same and I'm probably misunderstanding several concepts at this point.

I also tried setting some explicit lifetimes, but the syntax is still alien to me so I haven't been able to do it correctly IF that were the problem.

In the end it might be best to just separate the bytes and the parsed data... there might be a good reason as to why the authors of the Midly library decided to let the user read the bytes by hand, instead of holding the data within the Sfm struct, but I'm not too sure about that.

P.D: This is my first post, so any advice regarding the question itself is welcome.

Valazo
  • 31
  • 3
  • Aha, the obiquitous self-referential struct. – Chayim Friedman Apr 01 '22 at 06:25
  • In case it's not obvious how `Smf` is *meant* to be used: basically you can't write a single `from_file()` function. You're supposed to load the file yourself, somewhere in the caller, and store it in an owned container, like a `Vec`. Then your `MidiFile::from_u8(content: &'a [u8])` will attach the lifetime of the `MidiFile` to the scope of the container with the bytes. You can pass that structure around as long as you (statically) ensure that it doesn't outlive the `Vec` with the contents. – user4815162342 Apr 01 '22 at 12:18
  • If you know what you're doing, you can use unsafe to create a self-referencing struct as shown in [this answer](https://stackoverflow.com/a/67828823/1600898), but that's not something I'd recommend to Rust beginners. (As noted in the answer, there are crates that are supposed to help with that, but such crates are often vulnerable to soundness holes, and I believe in this case it's better to fully understand what's going on under the hood.) – user4815162342 Apr 01 '22 at 12:21
  • Well, since I definitely don't know what I'm doing yet, I guess I'll have to keep the data separated at least until I know more about the language. Thanks for the help! – Valazo Apr 01 '22 at 16:08

0 Answers0