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.

13
  • 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? Commented May 22, 2019 at 1: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. Commented May 22, 2019 at 1: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? Commented May 22, 2019 at 1:53
  • The linked code builds just fine. You've stated that the existing answer doesn't even compile, but that's demonstrably not true. Commented May 22, 2019 at 1: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 to help avoid these types of contortions. Commented May 22, 2019 at 2:01

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.