Stream: rust-toolchain

Topic: Rust FFI mapping


view this post on Zulip Yosh Wuyts (Jul 10 2026 at 14:52):

How far off am I with this mapping of Rust built-ins to the built-ins in Core Wasm and Wasm Components?

view this post on Zulip Yosh Wuyts (Jul 10 2026 at 14:52):

Rust Types Core Wasm Wasm Components
bool - bool
char - char
i8/i16/i32/i64/i128/isize -/-/i32/i64/-/i32 s8/s16/s32/s64/-/s32
u8/u16/u32/u64/u128/usize -/-/-/-/-/- u8/u16/u32/u64/-/u32
f32/f64 f32/f64 f32/f64
str/&str/String -/-/- -/-/string
[T; N]/&[T]/Vec<T> -/-/- -/-/list<T>
(T, U, ...) - tuple<...>
Option<T> - option<T>
Result<T, E> - result<T, E>
enum (without/with data) -/- enum/variant
struct { pub x, pub y } - record
struct { x, y }: Drop (owned/borrowed) - resource (own<T>/borrow<T>)
fn(...) -> T funcref func
async fn(...) -> T - async func
trait Logger/mod logger - interface logger

view this post on Zulip Yosh Wuyts (Jul 10 2026 at 14:55):

I don't know that much about core wasm, and whether for example we might ever get a native i8 equivalent type. So I'm trying to be as defensive as possible in the mappings here.

view this post on Zulip Yosh Wuyts (Jul 10 2026 at 14:56):

Also I feel the least confident in the func/async func/interface/resource mappings.

view this post on Zulip Yosh Wuyts (Jul 10 2026 at 15:05):

Also for some context here: for the Rust 2026 Wasm Component project goal we have "Experiment with #[repr(wasm)] and extern "wasm"" as a sub-goal. I've had someone volunteer to implement this, and they now have a basic hello-world compiling.

view this post on Zulip Alex Crichton (Jul 10 2026 at 15:07):

That looks mostly accurate to me, at least for components. Core wasm can have some updates (e.g. all integers are represented as wasm i32 or i64 except {i,u}128

view this post on Zulip Alex Crichton (Jul 10 2026 at 15:09):

for components though those analogs all seem roughly accurate, although [T; N] is more closely equivalent to list<T, N> which is an unstable feature at this moment (component-model-fixed-length-lists)

view this post on Zulip Yosh Wuyts (Jul 10 2026 at 15:10):

Alex Crichton said:

Core wasm can have some updates (e.g. all integers are represented as wasm i32 or i64 except {i,u}128

Do you mean that core wasm will e.g. never gain a native i8 type, so we probably should just hard-code it to always be an i32?

view this post on Zulip Alex Crichton (Jul 10 2026 at 15:11):

Well I wouldn't say never, but I can at least say right now it's mapped to i32 (ish)

view this post on Zulip Alex Crichton (Jul 10 2026 at 15:12):

a table like this eschews a lot of details about how things are actually represented, for example in-memory a Rust i8 is still mapped to a single byte, but when it's "on the stack" that's basically an implementation detail of LLVM which can do whatever it wants. When it's a function parameter that's defined by the C ABI and it's not just i32 but it's also sign/zero extended according to the original source language type

view this post on Zulip Yosh Wuyts (Jul 10 2026 at 15:15):

yeah I get that

view this post on Zulip Yosh Wuyts (Jul 10 2026 at 15:18):

Maybe I'm missing something you're implying with this tho?

view this post on Zulip Alex Crichton (Jul 10 2026 at 15:19):

It's sort of a question of what this table is in the context of. For example are you classifying:

#[link(wasm_import_module = "something")]
extern "C" {
    fn foo(some_type: SomeType);
}

you're changing SomeType, and seeing what pops out?

view this post on Zulip Yosh Wuyts (Jul 10 2026 at 15:19):

I'm very specifically trying to reason about what the representation of this at the ABI boundary; afaik that's separate from how LLVM ends up reasoning about those same values on stack compilation?

view this post on Zulip Alex Crichton (Jul 10 2026 at 15:19):

ok yeah for that core wasm follows this document

view this post on Zulip Alex Crichton (Jul 10 2026 at 15:20):

and for components it's a bit inverted where the chart here is sort of "this component model type is represented as this rust type in bindings generation"

view this post on Zulip Alex Crichton (Jul 10 2026 at 15:20):

as opposed to the core wasm direction of "this rust type is represented this way in core wasm ABI"

view this post on Zulip Yosh Wuyts (Jul 10 2026 at 15:21):

Do you reckon we need two ABIs here? One for core wasm (possibly versioned) and one for Components?

view this post on Zulip Yosh Wuyts (Jul 10 2026 at 15:21):

I'm saying "possibly versioned" because the doc states:

The current version of this ABI is 1.

view this post on Zulip Yosh Wuyts (Jul 10 2026 at 15:28):

Alex Crichton said:

It's sort of a question of what this table is in the context of. For example are you classifying:

#[link(wasm_import_module = "something")]
extern "C" {
    fn foo(some_type: SomeType);
}

you're changing SomeType, and seeing what pops out?

I'm still not sure I quite follow the question? I think you're asking whether this example is what I had in mind - and yeah, if you replace extern "C" with "extern "wasm" then that's pretty close!

view this post on Zulip Yosh Wuyts (Jul 10 2026 at 15:31):

e.g.

#[repr(wasm)]
struct SomeType(());

#[link(wasm_import_module = "something")]
unsafe extern "wasm" {
    fn foo(some_type: SomeType);
}

view this post on Zulip Alex Crichton (Jul 10 2026 at 15:41):

Well, personally, I still have no idea what repr(wasm) would be. It basically doesn't make sense to me beyond the high level "it helps wasm interop". I don't think it makes any sense at the core wasm level because that's what the C abi is already for, and for components it kind of doesn't make sense because it needs to specifically be defined in terms of component types, and then the ABI of functions is dicated by the canonical ABI signatures

view this post on Zulip Alex Crichton (Jul 10 2026 at 15:42):

I might just be lost, and I might be misleading here too

view this post on Zulip Yosh Wuyts (Jul 10 2026 at 16:38):

Oh you're good; don't worry!

view this post on Zulip Yosh Wuyts (Jul 10 2026 at 16:49):

Actually, maybe this is a better example of what I'm thinking of:

#[repr(wasm)]
struct SomeType(Result<String, String>);

#[link(wasm_import_module = "something")]
unsafe extern "wasm" {
    fn foo(some_type: SomeType);
}

view this post on Zulip Yosh Wuyts (Jul 10 2026 at 16:52):

repr(C) has no idea how to encode a Result or String. So we can't use those directly and we have to instead write code which converts Rust code to repr(C) code using the exact semantics that the component model expects.

view this post on Zulip Yosh Wuyts (Jul 10 2026 at 16:53):

My thinking was that #[repr(wasm)] would give us a built-in Rust way to say: "well, please just lay this out the way the component model would expect this to be laid out"

view this post on Zulip Yosh Wuyts (Jul 10 2026 at 16:55):

Extern blocks in Rust must be paired with repr attributes. If we omit them we get this safety warning:

   Compiling playground v0.0.1 (/playground)
warning: `extern` block uses type `SomeType`, which is not FFI-safe
 --> src/lib.rs:7:23
  |
7 |     fn foo(some_type: SomeType);
  |                       ^^^^^^^^ not FFI-safe
  |
  = help: consider adding a `#[repr(C)]` or `#[repr(transparent)]` attribute to this struct
  = note: this struct has unspecified layout
note: the type is defined here
 --> src/lib.rs:3:1
  |
3 | struct SomeType(());
  | ^^^^^^^^^^^^^^^
  = note: `#[warn(improper_ctypes)]` on by default

warning: `playground` (lib) generated 1 warning
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.81s

view this post on Zulip Yosh Wuyts (Jul 10 2026 at 16:56):

does that make more sense?

view this post on Zulip Christof Petig (Jul 10 2026 at 20:03):

I feel the reality of type mapping is more complex.

At the guest import level a string argument means &str, a string result is a Rust String. This inverts (sort of, because return types rely on cabi_post_f to release the "Rc") for guest exports.

This strange asymmetry caused by the guest host asymmetry and sandboxing motivated me to create the symmetric ABI between peers (without sandboxing).

view this post on Zulip Christof Petig (Jul 10 2026 at 20:04):

Similarly for Vec and &[T]

view this post on Zulip Yosh Wuyts (Jul 10 2026 at 20:39):

Oh yeah, good point on the asymmetry between imports and exports. I hadn't really looked at that closely, thank you for catching that!

view this post on Zulip Yosh Wuyts (Jul 10 2026 at 20:40):

Btw, does by "much more complex" you don't meant "intractable" right?

view this post on Zulip Yosh Wuyts (Jul 10 2026 at 20:41):

Happy to iterate and sus out complexity. But if folks here think this cannot/should not be done, I'd also like to hear it.

view this post on Zulip bjorn3 (Jul 10 2026 at 21:24):

I don't think fn() can map to funcref. An fn() is an index into a table. So returning an fn() back to rust would require allocating a new table entry, but there is no allocator for tables and afaik LLVM doesn't support modifying tables either.

view this post on Zulip bjorn3 (Jul 10 2026 at 21:28):

All #[repr] still require each field to be assigned some offset that you can take a reference to, but for #[repr(wasm)] struct SomeType(String); the String would suddenly have to get an entirely different layout matching the component model, which is not valid. The best you can do is have a core::ffi::WasmComponentString type or something like that.

view this post on Zulip bjorn3 (Jul 10 2026 at 21:30):

By the way if it has to match the component model, perhaps it should be called repr(wasm_component) and extern "wasm-component" or something like that? It is not representing core wasm types, but the component model canonical ABI.

view this post on Zulip Yosh Wuyts (Jul 10 2026 at 21:45):

All of that makes sense to me!

view this post on Zulip Yosh Wuyts (Jul 10 2026 at 21:48):

What im taking away from this is: what we probably actually want would be new the addition of new core::ffi types instead of repr(wasm)

view this post on Zulip Yosh Wuyts (Jul 10 2026 at 21:50):

That could potentially also be a way for us to add support for bitflags just for the component targets, without adding a general-purpose bitflags construct to Rust

view this post on Zulip Chris Fallin (Jul 10 2026 at 23:16):

I'll note that the component model type system is not fully compositional in the way that mapping from Rust types would expect: for example, one cannot borrow an enum that has a resource (== non-Copy struct) in one of its arms. I filed an issue and sketched out a generalization a few months ago; it entails seeing borrow<T> as a higher-order type constructor (as in Rust: & is conceptually a type-level function from Type to Type) rather than an argument modality (== second-class type).

All of that to say: while mapping very simple types may work out okay, I think there are deep mutual-expressability questions at the type system level that make this extremely difficult without potentially extending the component model (or alternately, having sharp cliffs where a type suddenly cannot be repr(wasm), in a way that is very surprising for Rust programmers)

view this post on Zulip Christof Petig (Jul 11 2026 at 06:39):

Yosh Wuyts said:

Btw, does by "much more complex" you don't meant "intractable" right?

I think it is very worth trying this because the usability benefits are high. But reading these excellent answers above it won't be straightforward and might require additions on both sides (like list<T,N> and ffi::ComponentString).


Last updated: Jul 29 2026 at 05:03 UTC