[wasm-bindgen] How to expose existent API which uses lifetimes to JS

Hello Community!

I have the rust library which has the following API:

// this object can be pretty big and keeps state during the program runs
let context: Context = Context::load_from_file(path);
let codec: Codec = context.get_codec(123).unwrap();
let data: Vec<u8> = codec.encode(input_str_data);

There are the following interfaces in Rust code

pub struct Codec<'a> {
    context: &'a Context,
    descriptor: &'a Descriptor,
}
impl<'a> Codec<'a> {
    pub fn encode(self, data: &str) -> Vec<u8> {
        todo!()
    }
}

pub struct Context {
    name: String,
    descriptors: std::collections::HashMap<u32, Descriptor>,
}
impl Context {
    pub fn get_codec<'a>(&'a self, num: &u32) -> Option<Codec> {
        self.descriptors.get(num).map(|descriptor|
            Codec { context: self, descriptor }
        )        
    }
}

struct Descriptor {
    name: String
}

And I'd like to expose this API to use it in JS,
Taking in to account lifetimes usage limitations in rustwasm - I managed to make the following wrapper:

use wasm_bindgen::prelude::*;

#[wasm_bindgen]
pub struct ContextWasm { 
    context: Context
}

#[wasm_bindgen]
impl ContextWasm {
    pub fn encode_with_num(&self, num: u32, data: &str) -> Vec<u8> {
        Codec {
            context: &self.context,
            // I have to lookup descriptor every time 
            descriptor: self.context.descriptors.get(&num).unwrap()
        }
        .encode(data)
    }
}

But here I changed API and do extra operations under the hood every time when I call encode_with_num as a code should find needed descriptor.

let contextwasm: ContextWasm = ContextWasm::load_from_file(path);
let data: Vec<u8> = contextwasm.encode_with_num(input_str_data, 123);

In my case it doesn't affect performance a lot. But I'd like to figure out if there are some approach to avoid this redundant operation.

Thank you in advance!

Types you expose to JavaScript can't have lifetimes because JavaScript objects can theoretically live forever (e.g. imagine you could turn the GC off), while lifetimes are explicitly designed to tie an object's lifetime to a specific stack frame and the lifetime of other variables.

This seems like a good place for crates like ouroboros or owning_ref that let you create self-referential structs (structs which own some data while other fields also reference that data). That way your ContextWasm owns everything and can be passed to JavaScript as much as you want.

#[self_referencing]
struct ContextWasm {
    context: Context,
    descriptor: Descriptor,
    #[borrows(context, descriptor)]
    codec: Codec<'this>,
}
2 Likes

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.