Funny story, we've actually wanted to do something similar at work 
If by FFI you mean declaring an extern "C"
function that is later provided by during linking then you can definitely do FFI when compiling to WebAssembly.
To work around a particular limitation in the glue generated by wit-bindgen
, we copied the way Rust's #[global_allocator]
works. In a shared crate we would declare some sort of fn __proc_block_metadata() -> Metadata
function and downstream crates would use a macro to generate a function with the correct name and signature.
Then, the compiler will compile each crate into an object file containing WebAssembly bytecode and at the very end the linker will take all these WebAssembly object files and link them together into a single WebAssembly binary, resolving our __proc_block_metadata
symbols in the process.
So in that sense, as long as you can get object files containing WebAssembly, it should theoretically be possible to manually link them together and resolve your FFI symbols.
I tried to do something similar about a year ago but in the opposite direction. I had a Rust crate and was using the cc
crate at build time to try to compile some C code to WebAssembly and link to it. Just like you would do when implementing a normal *-sys
crate.
I think I ran into some issues with linking to the object files or maybe it was because Emscripten uses its own ABI when compiling to WebAssembly. Either way I had other things on my plate at the time, so after half an hour of messing around I decided to throw it in the too hard basket and try something else.
If you squint hard, this is very similar to the solution from above, except the upstream dependency is an external C library instead of a Rust crate.
Until now, I've just talked about trying to compile multiple libraries into a single WebAssembly binary, but there is another solution.
When you load a WebAssembly binary (e.g. using WebAssembly.instantiate()
in the browser or wasmer
in native code), the binary can request certain functions be provided by the host.
However, there is nothing stopping the host from using the functions exported by one WebAssembly module as the imports of another. This lets you manually do dynamic linking and could be a solution for you.
The idea is you would compile your Rust code to a *.wasm
file and your C code goes in another *.wasm
file. At runtime, you would first instantiate the Rust binary then use its exported functions to satisfy the C binary's requirements.
We were originally hoping Wasmer could create a similar sort of static linking solution which takes two WebAssembly binaries and "merges" them, so I put together an experiment which shows how you do this dynamic linking manually, as well as writing up our goal in the README. The actual "linking" step happens on this line.
You can do something very similar in the browser and the flow is almost identical plus/minus some async
, but I don't have any handy links to source code for that 
Our final solution looks quite similar to this, except instead of writing the extern "C"
functions manually and having to try and smuggle high-level objects across an interface that only allows integers and floats, we use the wit-bindgen
project to declare high-level interfaces and generate glue code for the guest and host. The wit-bindgen
code generator also generates glue for a C guest, so that's a viable option for you too.
I don't know if that big wall of text answers your original question, but something like what you want is definitely possible to do. Hopefully some of those links and experiences can help you along the way.