2

When passing a string to a Rust WASM module, the passed data shows up as blank, as per the pattern matching in the real_code::compute function

The following code is what I've tried. I don't know if it has to do with how its being returned, but when I pass a hardcoded &str, it works fine. However, the JsInteropString shows as blank.

Here is how I encoded the string before sending it to WASM (from Passing a JavaScript string to a Rust function compiled to WebAssembly)

const memory = new WebAssembly.Memory({ initial: 20, 
                                        maximum: 100 });

const importObject = {
  env: { memory }
};

const memoryManager = (memory) => {
  var base = 0;

  // NULL is conventionally at address 0, so we "use up" the first 4
  // bytes of address space to make our lives a bit simpler.
  base += 4;

  return {
    encodeString: (jsString) => {
      // Convert the JS String to UTF-8 data
      const encoder = new TextEncoder();
      const encodedString = encoder.encode(jsString);

      // Organize memory with space for the JsInteropString at the
      // beginning, followed by the UTF-8 string bytes.
      const asU32 = new Uint32Array(memory.buffer, base, 2);
      const asBytes = new Uint8Array(memory.buffer, asU32.byteOffset + asU32.byteLength, encodedString.length);

      // Copy the UTF-8 into the WASM memory.
      asBytes.set(encodedString);

      // Assign the data pointer and length values.
      asU32[0] = asBytes.byteOffset;
      asU32[1] = asBytes.length;

      // Update our memory allocator base address for the next call
      const originalBase = base;
      base += asBytes.byteOffset + asBytes.byteLength;

      return originalBase;
    }
  };
};

invoking wasm like this:

//...loading and compiling WASM, getting instance from promise (standard)
const testStr = "TEST"
const input = myMemory.encodeString(testStr);
const offset = instance.exports.func_to_call(input);
// A struct with a known memory layout that we can pass string information in
#[repr(C)]
pub struct JsInteropString {
    data: *const u8,
    len: usize,
}

// Our FFI shim function
#[no_mangle]
pub unsafe extern "C" fn func_to_call(s: *const JsInteropString) -> *mut c_char {
    // ... check for nulls etc

    /*
    THROWS ERROR
    error[E0609]: no field `data` on type `*const JsInteropString`
    */
    //////let data = std::slice::from_raw_parts(s.data, s.len);

    //this fixes the above error
    let data = std::slice::from_raw_parts((*s).data, (*s).len);

    let dataToPrint: Result<_, _> = std::str::from_utf8(data);

    let real_res: &str = match dataToPrint {
        Ok(s) => real_code::compute(dataToPrint.unwrap()), //IS BLANK
        //Ok(s) => real_code::compute("SUCCESS"), //RETURNS "SUCCESS made it"
        Err(_) => real_code::compute("ERROR"),
    };

    unsafe {
        let s = CString::new(real_res).unwrap();
        println!("result: {:?}", &s);
        s.into_raw()
    }
}

mod real_code {
    pub fn compute(operator: &str) -> &str {
        match operator {
            "SUCCESS" => "SUCCESS made it",
            "ERROR" => "ERROR made it",
            "TEST" => "TEST made it",
            _ => operator,
        }
    }
}

When the function is called from JavaScript, it should return the same string passed. I even went as far as to update my Rust environment with rustup... any ideas?

UPDATE

Using a more representative version of the referenced post:

#[no_mangle]
pub unsafe extern "C" fn compute(s: *const JsInteropString) -> i32 {

    let s = match s.as_ref() {
        Some(s) => s,
        None => return -1,
    };

    // Convert the pointer and length to a `&[u8]`.
    let data = std::slice::from_raw_parts(s.data, s.len);

    // Convert the `&[u8]` to a `&str`    
    match std::str::from_utf8(data) {
        Ok(s) => real_code::compute(s),
        Err(_) => -2,
    }

}

mod real_code {
    pub fn compute(operator: &str) -> i32 {
        match operator {
            "SUCCESS"  => 1,
            "ERROR" => 2,
            "TEST" => 3,
            _ => 10,
        }
    }
}

regardless on the string passed through the js encoding, it still returns the default value from compute. I don't know if the referenced post actually solves the problem.

So yes, the code compiles. It is a runtime issue I am trying to solve. Something is off about how the string is being encoded and passed to WASM.

Update 2

Tried switching my toolchain to Nightly, and what I also found is that the following macros/attributes throw an error regarding unstable attributes

#![feature(wasm_import_memory)]
#![wasm_import_memory]

After realizing that I need to tell rust that memory will be imported, this seems to have fixed the problem of the string being passed as empty. It wasn't empty, it wasnt even passed it seems like.

Inside the .cargo file

[target.wasm32-unknown-unknown]
rustflags = [
    "-Clink-args=-s EXPORTED_FUNCTIONS=['_func_to_call'] -s ASSERTIONS=1",
    "-C", "link-args=--import-memory",
]

This seems to may have done the trick! @Shepmaster, update your answer from last year for others who stumble across it, as it has good SEO. This is due to changes in the rust environment.

Josh Lee
  • 171,072
  • 38
  • 269
  • 275
gadgethead
  • 31
  • 2
  • You've elided parts of the linked answer and in doing so cause problems that you have to then work around (*no field ... *). Why have you made these changes to the (presumably) working code? – Shepmaster May 22 '19 at 01:49
  • By the way, idiomatic Rust uses `snake_case` for variables, methods, macros, fields and modules; `UpperCamelCase` for types and enum variants; and `SCREAMING_SNAKE_CASE` for statics and constants. – Shepmaster May 22 '19 at 01:49
  • The no field error comes from the linked answer, even if I don't change anything that error occurs. I see the warnings when compiling for snake_case, etc and those are easy to fix. I don't think those are the issue here. The only changes I made from the linked code is the: let data = std::slice::from_raw_parts((*s).data, (*s).len); This line didn't compile by itself anyway. Do you have any idea regarding the question I asked? – gadgethead May 22 '19 at 01:53
  • The linked code [builds just fine](https://play.rust-lang.org/?version=nightly&mode=debug&edition=2018&gist=50c67af0778d430f9a37091c81f8cd14). You've stated that the existing answer doesn't even compile, but that's demonstrably not true. – Shepmaster May 22 '19 at 01:59
  • `Ok(s) => real_code::compute(dataToPrint.unwrap())` — just use `Ok(s) => real_code::compute(s)`. You may wish to re-read the relevant sections of [*The Rust Programming Language*](https://doc.rust-lang.org/book/) to help avoid these types of contortions. – Shepmaster May 22 '19 at 02:01
  • Why is there an `unsafe` block inside of your `unsafe` function? – Shepmaster May 22 '19 at 02:03
  • The verbatim original post builds fine, I wouldn't be posting this if it doesn't..... – gadgethead May 22 '19 at 02:06
  • Im saying the code I attached here doesn't build, but it is taking aspects of the post you linked... if you are going to respond, I'd prefer you actually try to answer my question.... plz whether i use dataToPrint.unwrap() or just dataToPrint it works fine, but this still doesn't answer the posted question... yes the linked post builds, but with these changes it does not. Can you actually help me debug this... even if I take out the unsafe inside the unsafe, it doesn't do anything for my question being answered... – gadgethead May 22 '19 at 02:09
  • ALSO, the code compiles just fine, its not a compilation problem shepmaster, its PURELY about the data being passed from javascript, showing up as empty during runtime..... please read my question carefully before you answer about specific tokens or lines of code. – gadgethead May 22 '19 at 02:10
  • I'm sorry that I've made you angry. Good luck solving your problem! – Shepmaster May 22 '19 at 02:11
  • you didn't make me angry, just trying to make sure we stay on task here... none of your answers were helpful towards the goal – gadgethead May 22 '19 at 02:13
  • I haven't provided **any** answers. I've only *commented* on issues with the code. That's the distinction between comments and answers on Stack Overflow. – Shepmaster May 22 '19 at 02:17
  • @Shepmaster updated the question even further, I think I figured it out. Your old post helped me think it through, but it wasn't as straight forward as it was back then. Still simple though. – gadgethead May 22 '19 at 17:12

0 Answers0