How do I make it so that the main function in WASM runs?

HTML

<body>
    <script type="module" src="js.js"></script>
  </body>

JS

import init, { main, add } from './pkg/basics.js'; 
async function run() {
    await init();
    const result = add(1, 2);
    console.log(`1 + 2 = ${result}`);
    if (result !== 3) throw new Error("wasm addition doesn't work!");
    main();
    
}
run();

Rust

use wasm_bindgen::prelude::*;
use std::fmt;
fn main() {
    struct Point {
        x: i32,
        y: i32,
    }
    impl fmt::Display for Point {
        fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
            write!(f, "({}, {})", self.x, self.y)
        }
    }
    let origin = Point { x: 0, y: 0 };
    println!("Hello, world!");
    println!("The origin is: {}", origin);
}
#[wasm_bindgen]
pub fn add(a: u32, b: u32) -> u32 {
    a + b
}

I've also tried:

#[wasm_bindgen]
pub fn main() { ... }

But that doesn't work either.

The MDN Rust To Wasm Guide
does not give a plain example without wasm-bindgen.

If you try it plain without wasm-bindgen and do what the other examples on the MDN wasm docs show, you will do this thing in java with an "importObject" and you will get errors if you try it that way with rust. The trick I found is that with Rust, your java importObject must have the stuff put in an env{} wrap.

Here is a simple rust program. Compile to wasm with

cargo build --target wasm32-unknown-unknown

and put the compiled lib someplace the javascript can find it.

lib.rs

use std::ffi::c_void;

#[no_mangle]
pub extern "C" fn rust_hello() {
    unsafe {
        imported::say_hi();
        imported::say_this("hello" as *const str as *const c_void, "hello".len());
        imported::console_log("HELLO" as *const str as *const c_void, "HELLO".len());
    }
}

mod imported {
    use super::*;

    extern "C" {
        pub fn say_hi();
    }

    extern "C" {
        pub fn say_this(msg: *const c_void, length: usize);
    }

    extern "C" {
        pub fn console_log(msg: *const c_void, length: usize);
    }
}

and a simple javascript
NOTE: the importObject with the word "env" and the stuff in brackets. The simple.wasm example on MDN does not have that "env" part. Rust needs it.
Also fix the path to your wasm file.

scripts/main.js

function say_hi() {
        alert("Hi there from javascript");
}

function say_this (ptr, len) {
        const msg = new Uint8Array(wasm_shared.buffer,ptr,len);
        alert( msg);
}

var wasm_shared;

var importObject = {
        env : {
                say_hi : say_hi,
                say_this : say_this,
                console_log : console.log,
        }
}

WebAssembly.instantiateStreaming(fetch('path/to/hello.wasm'), importObject)
.then(results => {
        wasm_shared = results.instance.exports.memory;
        results.instance.exports.rust_hello();
})

call them with a plain index.html

<!DOCTYPE html>
<html lang="en">
        <head>
                <meta charset="utf-8">
        </head>
        <body>
                <script src="scripts/main.js">
                </script>
        </body>
</html>

Also this is a plain minimal example. The popups will show the numbers for the words "hello" "HELLO" because we passed in u8's As javascript and wasm talk to each other with numbers. If you want the javascript to understand the numbers as words you will need more javascript "TextDecoder" or something.

which tool did you choose to compile and bundle, wasm-pack or trunk? or just cargo? @Evan

I see, somewhat.
Do you have any recommendations to get a better grip on these things? Where can I learn these things myself, since reading documentation doesn't say that much besides how to literally implement something.
Do I need to first learn C++? Is there a good book which you'd recommend? Or do I need to go more in depth with WASM?

For example:

use std::ffi::c_void;

#[no_mangle]

I've never seen. How do I come up with these things? I feel like I can keep going and messing around, but it's going to be very slow, and I need continues answer on seemingly simple things. :grimacing:

You should probably first learn just Rust, read the book, write a few command line programs. And then learn how to target WASM from Rust.

2 Likes

Get a grip...What I do is read everything over and over and try to play example code and look at what happens. With the part about "env", I found that in the kettle11 example and did not understand why? Still do not understand why. But I did look into it a bit. I made a simple rust lib with one function then turned the wasm file into wat (wasm text) In that i see the env is needed. I guess rust wraps the imports in an "env" but where is that documented? I think the best way is to sayl. "It is not documented." (maybe the internet will correct me?)

Oh yeah, before I forget... you may or may not be able to invoke "main()" in wasm land. The examples I have read that do cross platform, wasm / native use a main() for the native and a lib.rs function for the wasm. The egui demo native has a main() and the egui demo web uses a start() function and it is controlled by some flags that look like

#[cfg(target_arch = "wasm32")]

That include code or not depending.

Now back to your question...

The "use std...." line is because in the rust program I used c_void and the word "c_void" is not defined in my program. So I bring in the "c_void" thing from std::ffi and use it. There is docs and if you have rust installed you can type

rustup doc std::ffi::c_void

same for other docs in the std library.

The other line #[no_mangle] is a thing called an attribute and the doc may be to much for you now... But the attribute "no_mangle" means that it does not change the names of the things. For example, your program may have a function named "bob()" in a library and another library also has a "bob()" function. When rust compiles it "mangles" the names in the binary so they are stored in the binary as something other than just "bob". "no_mangle" makes is to the compiler just leaves the following object with the same name as they are written. This will give an error if there are two "no_mangled" bob() functions. But in this case it makes the compiled binary have names that match the text they are written in. This makes it possible to call them from javascript without javascript having to do the magic of figuring out what the default rust name mangle does...(and nobody knows how rust mangles names because it is defined as undefined.

1 Like

Yeah, for sure type every example in the book.
And rustlings if you can.

Extra credit if you compile and run the book work in wasm too.

1 Like

Thanks again for your thorough explanation. It definitely helps me.
I'll keep at it ^^

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.