0

I am completely new to rust but have prior experience in C and I have this rust code which does not compile.

use std::fs;

static mut GRAPH_BUILDER: Option<Box<[&[u8]; 2]>> = None;

#[export_name = "wizer.initialize"]
pub extern "C" fn init() {
    let weights = fs::read("./mobilenet.bin").unwrap();
    let xml = fs::read_to_string("./mobilenet.xml").unwrap().into_bytes();
    let input: [&[u8]; 2] = [&weights, &xml];

    unsafe {
        GRAPH_BUILDER = Some(Box::new(input));
    }
}

pub fn main() {
    unsafe {
        if GRAPH_BUILDER.is_none() {
            init();
        }
    }
}

When compiled with rustc main.rs It produces these errors

error[E0597]: `weights` does not live long enough
  --> main.rs:9:30
   |
9  |     let input: [&[u8]; 2] = [&weights, &xml];
   |                              ^^^^^^^^ borrowed value does not live long enough
...
12 |         GRAPH_BUILDER = Some(Box::new(input));
   |                              --------------- argument requires that `weights` is borrowed for `'static`
13 |     }
14 | }
   | - `weights` dropped here while still borrowed

error[E0597]: `xml` does not live long enough
  --> main.rs:9:40
   |
9  |     let input: [&[u8]; 2] = [&weights, &xml];
   |                                        ^^^^ borrowed value does not live long enough
...
12 |         GRAPH_BUILDER = Some(Box::new(input));
   |                              --------------- argument requires that `xml` is borrowed for `'static`
13 |     }
14 | }
   | - `xml` dropped here while still borrowed

error: aborting due to 2 previous errors

For more information about this error, try `rustc --explain E0597`.

I understand that this error is caused because weights is stored on the stack and will be unallocated when the init function terminates, so the static GRAPH_BUILDER would contain a pointer to unallocated memory. So I tried to use a Box instead, which from my understanding should act like malloc in C. But it does not seem to work. How can I get rust to allocate the weights and xml variables on the heap, without knowing their sizes at compile time? I.e. What is the rust-equivalent of malloc?

I have read here that you can call clone to fix this problem. But I don't want to do it because the files I read in are large. And I also don't understand why that would fix the problem. Wouldn't the clone just end up on the stack too?

EDIT: Another important thing I should mention, I need to provide GRAPH_BUILDER as an argument to the wasi_nn::load function, and it essentially expects it to be of the datatype slice &[&[u8]]. But I have absolutely no idea how to convert it to this datatype. Here is the continuation of the main function.

use wasi_nn;

pub extern "C" fn main() {
    bench::start();
    let context: wasi_nn::GraphExecutionContext;
    unsafe {
        if GRAPH_BUILDER.is_none() {
            init();
        }
    }

    let graph = unsafe {
        wasi_nn::load(
            &GRAPH_BUILDER.expect("Graph builder was not initialized"),
            wasi_nn::GRAPH_ENCODING_OPENVINO,
            wasi_nn::EXECUTION_TARGET_CPU,
        )
        .unwrap()
    };

I am trying to modify this code such that it creates &[&xml.into_bytes(), &weights] in an initialization function instead of passing it directly.

Mechakhan
  • 25
  • 4

1 Answers1

2

What is the rust-equivalent of malloc?

That is a completely unnecessary question, you don't need to dig further, you need to actually look at your code and think about what's happening.

Rust's &T is not a C-style wild pointer, it's a lexically-scoped borrowing smart pointer. It can only refer to data that is lexically valid for its entire lifespan.

So I tried to use a Box instead, which from my understanding should act like malloc in C. But it does not seem to work.

Well no, because inside your box you're still putting references to function-local data. Which is what Rust is complaining about.

You're not copying or moving that data to the heap you're just adding an indirection to the exact same issue: when the function ends, weights and xml are still going to be dropped, and the references you put inside your Box will still be dangling, which Rust does not allow.

But weights and xml are Vec<u8> so they're already owned, heap-allocated data types. All you need to do is move them into your thing:

static mut GRAPH_BUILDER: Option<(Vec<u8>, Vec<u8>)> = None;

[...]

        GRAPH_BUILDER = Some((weights, xml));

Also you want to unwrap in an extern C function even less than in a Rust function. To my knowledge, at best that will summarily abort, at worst it's UB.

Masklinn
  • 34,759
  • 3
  • 38
  • 57
  • Thank you, your solution does compile. I realized though that I needed to add some more context to my question, which I have now done. Is there a way to convert between `(Vec, Vec)` and `[&[u8]]`? – Mechakhan Apr 04 '23 at 08:24
  • An unrelated question, if `xml` and `weight` are `Vec` and `Vec` is a heap-allocated data type, then theoretically they don't need to be dropped at the end of the init function (Assuming I assigned a pointer to them to a static variable). In C I would just not `free` them, can this be achieved in Rust too? – Mechakhan Apr 04 '23 at 09:07
  • Why do you want to perform such a conversion? Also why are you so intent on trying to get perpetual references? No offense but you seem rather insistant on bad ideas and working against the language. – Masklinn Apr 04 '23 at 11:15
  • Did you check the edit I did in the original question? If the wasi_nn::load function wants a `&[&[u8]]` then what am i supposed to do about it, apart converting the input to that datatype? It's not as if I am trying to work against the language. – Mechakhan Apr 04 '23 at 12:23
  • @Mechakhan no I'd not seen it. You can just create a slice at load time by borrowing the owned vecs, something like [this](https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=6b8bed18f350d28f930fd8dabecd09ed), the function takes slices of references but it doesn't actually keep a reference so that should not be an issue. I've no idea why you would want to go from all-safe all-sensible all-locals to a global mutable static tho. Some sort of cache? – Masklinn Apr 04 '23 at 14:38
  • Thanks a lot, works perfectly! Yes, the reason I'm doing this is to essentially cache the initialization code using a tool called Wizer which is meant to pre-initialize WebAssembly modules for faster startup. – Mechakhan Apr 04 '23 at 17:29