Repository: bytecodealliance/wasmtime
Pull request #5276 (2022-11-15 23:45:19)

wiggle: new error configuration for generating a "trappable error"
Largely due to pains in wasi-common and experimentation in wit-bindgen, I have added a slightly different error handling strategy to `wiggle`.

When the user specifies `errno => trappable Error` in the macro's optional `errors: {...}`argument, wiggle will now generate a type named `Error` to be used in all positions where a function returned a witx `errno` in the error position.

The generated error type is an opaque struct. Internally, it contains either the errno (which wiggle generates as an `enum Errno`), or an `anyhow::Error` for trapping execution. These are constructed using either the generated `impl From<Errno> for Error` impl, or the `Error::trap(t: anyhow::Error) -> Self` constructor.

The error can be destructured using `Error::downcast(self) -> Result<Errno, anyhow::Error>`, inspected using `Error::downcast_ref(&self) -> Option<&Errno>`, and it can have context added via `Error::context(self, s: Into<String>) -> Self`. The context is only observable via the Debug and Display impls.

This design came about because the user defined error type wasn't really serving our needs in `wasi-common`. There, we had essentially 2 choices:
1. the user maintains a `thiserror` style error enum that contains all of the `errno` values, plus a `Trap(anyhow::Error)` variation for trapping execution. They then write `From` impls for all of the foreign error types that need to be converted into their error enum, e.g. `impl From<std::io::Error> for MyErrno`. They then implement the (very mechanical) translation to the wiggle generated `Errno` cases in `UserErrorConversion`.
2. They use `anyhow::Error`. To implement the translation to `Errno` or trap, the user downcasts all of the foreign error types they suspect may be thrown in their implementations.

I used both of these designs in wasi-common, in that chronological order. I found that both had real drawbacks:
1. This error enum is repetitive to define and maintain. Additionally, it doesn't provide any way to keep additional context around on an error, which makes debugging harder.
2. Suffers from safety problems. Since introducing this design in the 2019 wasi-common rewrite, I (unknowingly) introduced a bug where a `cap_rand::Error` was thrown in the random methods, but I never tried to downcast to it in `UserErrorConversion`. The end result is that any error in the cap_rand crate would end up trapping execution of the wasm module, rather than returning it an appropriate errno. I don't think I ever would have discovered this bug if I hadn't attempted a refactor back towards design 1.

So, my design here basically uses code generation to generate an acceptable implementation of design 1, while providing the `.context("help figuring out what went wrong")` method available in design 2. I was reluctant to give up the ergonomics of approach 2, but the design of wasi is evolving to give us lots of different concrete errnos in the preview 2 interfaces, so I wanted to give us as much type safety as possible when managing the complexity of the coming evolution in this crate.


Patch:
diff --git a/Cargo.toml b/Cargo.toml
index 235f312b3..605695b9c 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -225,3 +225,7 @@ harness = false
 [[bench]]
 name = "call"
 harness = false
+
+[[bench]]
+name = "wasi"
+harness = false
diff --git a/benches/call.rs b/benches/call.rs
index f1e5790b5..9ec51d35d 100644
--- a/benches/call.rs
+++ b/benches/call.rs
@@ -117,7 +117,7 @@ fn bench_host_to_wasm<Params, Results>(
     // below.
     c.bench_function(&format!("host-to-wasm - typed - {}", name), |b| {
         let typed = instance
-            .get_typed_func::<Params, Results, _>(&mut *store, name)
+            .get_typed_func::<Params, Results>(&mut *store, name)
             .unwrap();
         b.iter(|| {
             let results = if is_async.use_async() {
@@ -370,7 +370,7 @@ fn wasm_to_host(c: &mut Criterion) {
     ) {
         group.bench_function(&format!("wasm-to-host - {} - nop", desc), |b| {
             let run = instance
-                .get_typed_func::<u64, (), _>(&mut *store, "run-nop")
+                .get_typed_func::<u64, ()>(&mut *store, "run-nop")
                 .unwrap();
             b.iter_custom(|iters| {
                 let start = Instant::now();
@@ -386,7 +386,7 @@ fn wasm_to_host(c: &mut Criterion) {
             &format!("wasm-to-host - {} - nop-params-and-results", desc),
             |b| {
                 let run = instance
-                    .get_typed_func::<u64, (), _>(&mut *store, "run-nop-params-and-results")
+                    .get_typed_func::<u64, ()>(&mut *store, "run-nop-params-and-results")
                     .unwrap();
                 b.iter_custom(|iters| {
                     let start = Instant::now();
diff --git a/benches/thread_eager_init.rs b/benches/thread_eager_init.rs
index 9f6e4c5b9..8572a335c 100644
--- a/benches/thread_eager_init.rs
+++ b/benches/thread_eager_init.rs
@@ -4,7 +4,7 @@ use std::time::{Duration, Instant};
 use wasmtime::*;
 
 fn measure_execution_time(c: &mut Criterion) {
-    // Baseline performance: a single measurment covers both initializing
+    // Baseline performance: a single measurement covers both initializing
     // thread local resources and executing the first call.
     //
     // The other two bench functions should sum to this duration.
@@ -55,7 +55,7 @@ fn duration_of_call(engine: &Engine, module: &Module) -> Duration {
     let mut store = Store::new(engine, ());
     let inst = Instance::new(&mut store, module, &[]).expect("instantiate");
     let f = inst.get_func(&mut store, "f").expect("get f");
-    let f = f.typed::<(), (), _>(&store).expect("type f");
+    let f = f.typed::<(), ()>(&store).expect("type f");
 
     let call = Instant::now();
     f.call(&mut store, ()).expect("call f");
diff --git a/benches/trap.rs b/benches/trap.rs
index 933c77e16..979f30676 100644
--- a/benches/trap.rs
+++ b/benches/trap.rs
@@ -38,9 +38,8 @@ fn bench_multi_threaded_traps(c: &mut Criterion) {
                                 move || {
                                     let mut store = Store::new(&engine, ());
                                     let instance = Instance::new(&mut store, &module, &[]).unwrap();
-                                    let f = instance
-                                        .get_typed_func::<(), (), _>(&mut store, "")
-                                        .unwrap();
+                                    let f =
+                                        instance.get_typed_func::<(), ()>(&mut store, "").unwrap();
 
                                     // Notify the parent thread that we are
                                     // doing background work now.
@@ -67,9 +66,7 @@ fn bench_multi_threaded_traps(c: &mut Criterion) {
 
                     let mut store = Store::new(&engine, ());
                     let instance = Instance::new(&mut store, &module, &[]).unwrap();
-                    let f = instance
-                        .get_typed_func::<(), (), _>(&mut store, "")
-                        .unwrap();
+                    let f = instance.get_typed_func::<(), ()>(&mut store, "").unwrap();
 
                     // Measure how long it takes to do `iters` worth of traps
                     // while there is a bunch of background work going on.
@@ -111,9 +108,7 @@ fn bench_many_modules_registered_traps(c: &mut Criterion) {
                 b.iter_custom(|iters| {
                     let mut store = Store::new(&engine, ());
                     let instance = Instance::new(&mut store, modules.last().unwrap(), &[]).unwrap();
-                    let f = instance
-                        .get_typed_func::<(), (), _>(&mut store, "")
-                        .unwrap();
+                    let f = instance.get_typed_func::<(), ()>(&mut store, "").unwrap();
 
                     let start = std::time::Instant::now();
                     for _ in 0..iters {
@@ -143,9 +138,7 @@ fn bench_many_stack_frames_traps(c: &mut Criterion) {
                 b.iter_custom(|iters| {
                     let mut store = Store::new(&engine, ());
                     let instance = Instance::new(&mut store, &module, &[]).unwrap();
-                    let f = instance
-                        .get_typed_func::<(), (), _>(&mut store, "")
-                        .unwrap();
+                    let f = instance.get_typed_func::<(), ()>(&mut store, "").unwrap();
 
                     let start = std::time::Instant::now();
                     for _ in 0..iters {
@@ -204,7 +197,7 @@ fn bench_host_wasm_frames_traps(c: &mut Criterion) {
                     );
                     let instance = Instance::new(&mut store, &module, &[host_func.into()]).unwrap();
                     let f = instance
-                        .get_typed_func::<(i32,), (), _>(&mut store, "f")
+                        .get_typed_func::<(i32,), ()>(&mut store, "f")
                         .unwrap();
 
                     let start = std::time::Instant::now();
diff --git a/benches/wasi.rs b/benches/wasi.rs
new file mode 100644
index 000000000..3b8f28863
--- /dev/null
+++ b/benches/wasi.rs
@@ -0,0 +1,71 @@
+//! Measure some common WASI call scenarios.
+
+use criterion::{criterion_group, criterion_main, Criterion};
+use std::time::Instant;
+use wasmtime::{Engine, Linker, Module, Store, TypedFunc};
+use wasmtime_wasi::{sync::WasiCtxBuilder, WasiCtx};
+
+criterion_group!(benches, bench_wasi);
+criterion_main!(benches);
+
+fn bench_wasi(c: &mut Criterion) {
+    // Benchmark each `*.wat` file in the `wasi` directory.
+    for file in std::fs::read_dir("benches/wasi").unwrap() {
+        let path = file.unwrap().path();
+        if path.extension().map(|e| e == "wat").unwrap_or(false) {
+            let wat = std::fs::read(&path).unwrap();
+            let (mut store, run_fn) = instantiate(&wat);
+            let bench_name = format!("wasi/{}", path.file_name().unwrap().to_string_lossy());
+            // To avoid overhead, the module itself must iterate the expected
+            // number of times in a specially-crafted `run` function (see
+            // `instantiate` for details).
+            c.bench_function(&bench_name, move |b| {
+                b.iter_custom(|iters| {
+                    let start = Instant::now();
+                    let result = run_fn.call(&mut store, iters).unwrap();
+                    assert_eq!(iters, result);
+                    start.elapsed()
+                })
+            });
+        }
+    }
+}
+
+/// Compile and instantiate the Wasm module, returning the exported `run`
+/// function. This function expects `run` to:
+/// - have a single `u64` parameter indicating the number of loop iterations to
+///   execute
+/// - execute the body of the function for that number of loop iterations
+/// - return a single `u64` indicating how many loop iterations were executed
+///   (to double-check)
+fn instantiate(wat: &[u8]) -> (Store<WasiCtx>, TypedFunc<u64, u64>) {
+    let engine = Engine::default();
+    let wasi = wasi_context();
+    let mut store = Store::new(&engine, wasi);
+    let module = Module::new(&engine, wat).unwrap();
+    let mut linker = Linker::new(&engine);
+    wasmtime_wasi::add_to_linker(&mut linker, |cx| cx).unwrap();
+    let instance = linker.instantiate(&mut store, &module).unwrap();
+    let run = instance.get_typed_func(&mut store, "run").unwrap();
+    (store, run)
+}
+
+/// Build a WASI context with some actual data to retrieve.
+fn wasi_context() -> WasiCtx {
+    let wasi = WasiCtxBuilder::new();
+    wasi.envs(&[
+        ("a".to_string(), "b".to_string()),
+        ("b".to_string(), "c".to_string()),
+        ("c".to_string(), "d".to_string()),
+    ])
+    .unwrap()
+    .args(&[
+        "exe".to_string(),
+        "--flag1".to_string(),
+        "--flag2".to_string(),
+        "--flag3".to_string(),
+        "--flag4".to_string(),
+    ])
+    .unwrap()
+    .build()
+}
diff --git a/benches/wasi/get-current-time.wat b/benches/wasi/get-current-time.wat
new file mode 100644
index 000000000..8ac6ceb07
--- /dev/null
+++ b/benches/wasi/get-current-time.wat
@@ -0,0 +1,22 @@
+(module
+    (import "wasi_snapshot_preview1" "clock_time_get"
+        (func $__wasi_clock_time_get (param i32 i64 i32) (result i32)))
+    (func (export "run") (param $iters i64) (result i64)
+        (local $i i64)
+        (local.set $i (i64.const 0))
+        (loop $cont
+            ;; Retrieve the current time with the following parameters:
+            ;; - $clockid: here we use the enum value for $realtime
+            ;; - $precision: the maximum lag, which we set to 0 here
+            ;; - the address at which to write the u64 $timestamp
+            ;; Returns an error code.
+            (call $__wasi_clock_time_get (i32.const 1) (i64.const 0) (i32.const 0))
+            (drop)
+            ;; Continue looping until $i reaches $iters.
+            (local.set $i (i64.add (local.get $i) (i64.const 1)))
+            (br_if $cont (i64.lt_u (local.get $i) (local.get $iters)))
+        )
+        (local.get $i)
+    )
+    (memory (export "memory") 1)
+)
diff --git a/benches/wasi/read-arguments.wat b/benches/wasi/read-arguments.wat
new file mode 100644
index 000000000..c6f1d4a33
--- /dev/null
+++ b/benches/wasi/read-arguments.wat
@@ -0,0 +1,42 @@
+(module
+    (import "wasi_snapshot_preview1" "args_get"
+        (func $__wasi_args_get (param i32 i32) (result i32)))
+    (import "wasi_snapshot_preview1" "args_sizes_get"
+        (func $__wasi_args_sizes_get (param i32 i32) (result i32)))
+    (func (export "run") (param $iters i64) (result i64)
+        (local $i i64)
+        (local.set $i (i64.const 0))
+        (loop $cont
+            ;; Read the current argument list by:
+            ;;  1) retrieving the argument sizes and then
+            ;;  2) retrieving the argument data itself.
+
+            ;; Retrieve the sizes of the arguments with parameters:
+            ;; - the address at which to write the number of arguments
+            ;; - the address at which to write the size of the argument buffer
+            ;; Returns an error code.
+            (call $__wasi_args_sizes_get (i32.const 0) (i32.const 4))
+            (drop)
+
+            ;; Read the arguments with parameters:
+            ;; - the address at which to write the array of argument pointers
+            ;;   (i.e., one pointer per argument); here we overwrite the size
+            ;;   written at address 0
+            ;; - the address at which to write the buffer of argument strings
+            ;;   (pointed to by the items written to the first address); we
+            ;;   calculate where to start the buffer based on the size of the
+            ;;   pointer list (i.e., number of arguments * 4 bytes per pointer)
+            ;; Returns an error code.
+            (call $__wasi_args_get
+                (i32.const 0)
+                (i32.mul (i32.load (i32.const 0)) (i32.const 4)))
+            (drop)
+
+            ;; Continue looping until $i reaches $iters.
+            (local.set $i (i64.add (local.get $i) (i64.const 1)))
+            (br_if $cont (i64.lt_u (local.get $i) (local.get $iters)))
+        )
+        (local.get $i)
+    )
+    (memory (export "memory") 1)
+)
diff --git a/benches/wasi/read-environment.wat b/benches/wasi/read-environment.wat
new file mode 100644
index 000000000..50f50b227
--- /dev/null
+++ b/benches/wasi/read-environment.wat
@@ -0,0 +1,45 @@
+(module
+    (import "wasi_snapshot_preview1" "environ_get"
+        (func $__wasi_environ_get (param i32 i32) (result i32)))
+    (import "wasi_snapshot_preview1" "environ_sizes_get"
+        (func $__wasi_environ_sizes_get (param i32 i32) (result i32)))
+    (func (export "run") (param $iters i64) (result i64)
+        (local $i i64)
+        (local.set $i (i64.const 0))
+        (loop $cont
+            ;; Read the current environment key-value pairs by:
+            ;;  1) retrieving the environment sizes and then
+            ;;  2) retrieving the environment data itself.
+
+            ;; Retrieve the sizes of the environment with parameters:
+            ;; - the address at which to write the number of environment
+            ;;   variables
+            ;; - the address at which to write the size of the environment
+            ;;   buffer
+            ;; Returns an error code.
+            (call $__wasi_environ_sizes_get (i32.const 0) (i32.const 4))
+            (drop)
+
+            ;; Read the environment with parameters:
+            ;; - the address at which to write the array of environment pointers
+            ;;   (i.e., one pointer per key-value pair); here we overwrite
+            ;;   the size written at address 0
+            ;; - the address at which to write the buffer of key-value pairs
+            ;;   (pointed to by the items written to the first address); we
+            ;;   calculate where to start the buffer based on the size of the
+            ;;   pointer list (i.e., number of key-value pairs * 4 bytes per
+            ;;   pointer)
+            ;; Returns an error code.
+            (call $__wasi_environ_get
+                (i32.const 0)
+                (i32.mul (i32.load (i32.const 0)) (i32.const 4)))
+            (drop)
+
+            ;; Continue looping until $i reaches $iters.
+            (local.set $i (i64.add (local.get $i) (i64.const 1)))
+            (br_if $cont (i64.lt_u (local.get $i) (local.get $iters)))
+        )
+        (local.get $i)
+    )
+    (memory (export "memory") 1)
+)
diff --git a/crates/bench-api/src/lib.rs b/crates/bench-api/src/lib.rs
index 498236f64..60291d24f 100644
--- a/crates/bench-api/src/lib.rs
+++ b/crates/bench-api/src/lib.rs
@@ -533,7 +533,7 @@ impl BenchState {
             .take()
             .expect("instantiate the module before executing it");
 
-        let start_func = instance.get_typed_func::<(), (), _>(&mut store, "_start")?;
+        let start_func = instance.get_typed_func::<(), ()>(&mut store, "_start")?;
         match start_func.call(&mut store, ()) {
             Ok(_) => Ok(()),
             Err(trap) => {
diff --git a/crates/fuzzing/src/oracles/stacks.rs b/crates/fuzzing/src/oracles/stacks.rs
index 3216ed10c..8705b5887 100644
--- a/crates/fuzzing/src/oracles/stacks.rs
+++ b/crates/fuzzing/src/oracles/stacks.rs
@@ -53,7 +53,7 @@ pub fn check_stacks(stacks: Stacks) -> usize {
         .expect("should instantiate okay");
 
     let run = instance
-        .get_typed_func::<(u32,), (), _>(&mut store, "run")
+        .get_typed_func::<(u32,), ()>(&mut store, "run")
         .expect("should export `run` function");
 
     let mut max_stack_depth = 0;
@@ -62,7 +62,7 @@ pub fn check_stacks(stacks: Stacks) -> usize {
         if let Err(trap) = run.call(&mut store, (input.into(),)) {
             log::debug!("trap: {:?}", trap);
             let get_stack = instance
-                .get_typed_func::<(), (u32, u32), _>(&mut store, "get_stack")
+                .get_typed_func::<(), (u32, u32)>(&mut store, "get_stack")
                 .expect("should export `get_stack` function as expected");
 
             let (ptr, len) = get_stack
diff --git a/crates/wasmtime/src/func.rs b/crates/wasmtime/src/func.rs
index 587fd8e56..ecc3f22b4 100644
--- a/crates/wasmtime/src/func.rs
+++ b/crates/wasmtime/src/func.rs
@@ -95,7 +95,7 @@ use wasmtime_runtime::{
 /// // ... or we can make a static assertion about its signature and call it.
 /// // Our first call here can fail if the signatures don't match, and then the
 /// // second call can fail if the function traps (like the `match` above).
-/// let foo = foo.typed::<(), (), _>(&store)?;
+/// let foo = foo.typed::<(), ()>(&store)?;
 /// foo.call(&mut store, ())?;
 /// # Ok(())
 /// # }
@@ -130,7 +130,7 @@ use wasmtime_runtime::{
 ///     "#,
 /// )?;
 /// let instance = Instance::new(&mut store, &module, &[add.into()])?;
-/// let call_add_twice = instance.get_typed_func::<(), i32, _>(&mut store, "call_add_twice")?;
+/// let call_add_twice = instance.get_typed_func::<(), i32>(&mut store, "call_add_twice")?;
 ///
 /// assert_eq!(call_add_twice.call(&mut store, ())?, 10);
 /// # Ok(())
@@ -603,7 +603,7 @@ impl Func {
     ///     "#,
     /// )?;
     /// let instance = Instance::new(&mut store, &module, &[add.into()])?;
-    /// let foo = instance.get_typed_func::<(i32, i32), i32, _>(&mut store, "foo")?;
+    /// let foo = instance.get_typed_func::<(i32, i32), i32>(&mut store, "foo")?;
     /// assert_eq!(foo.call(&mut store, (1, 2))?, 3);
     /// # Ok(())
     /// # }
@@ -634,7 +634,7 @@ impl Func {
     ///     "#,
     /// )?;
     /// let instance = Instance::new(&mut store, &module, &[add.into()])?;
-    /// let foo = instance.get_typed_func::<(i32, i32), i32, _>(&mut store, "foo")?;
+    /// let foo = instance.get_typed_func::<(i32, i32), i32>(&mut store, "foo")?;
     /// assert_eq!(foo.call(&mut store, (1, 2))?, 3);
     /// assert!(foo.call(&mut store, (i32::max_value(), 1)).is_err());
     /// # Ok(())
@@ -672,7 +672,7 @@ impl Func {
     ///     "#,
     /// )?;
     /// let instance = Instance::new(&mut store, &module, &[debug.into()])?;
-    /// let foo = instance.get_typed_func::<(), (), _>(&mut store, "foo")?;
+    /// let foo = instance.get_typed_func::<(), ()>(&mut store, "foo")?;
     /// foo.call(&mut store, ())?;
     /// # Ok(())
     /// # }
@@ -720,7 +720,7 @@ impl Func {
     ///     "#,
     /// )?;
     /// let instance = Instance::new(&mut store, &module, &[log_str.into()])?;
-    /// let foo = instance.get_typed_func::<(), (), _>(&mut store, "foo")?;
+    /// let foo = instance.get_typed_func::<(), ()>(&mut store, "foo")?;
     /// foo.call(&mut store, ())?;
     /// # Ok(())
     /// # }
@@ -1178,10 +1178,6 @@ impl Func {
     /// function. This behaves the same way as `Params`, but just for the
     /// results of the function.
     ///
-    /// The `S` type parameter represents the method of passing in the store
-    /// context, and can typically be specified as simply `_` when calling this
-    /// function.
-    ///
     /// Translation between Rust types and WebAssembly types looks like:
     ///
     /// | WebAssembly | Rust                |
@@ -1232,7 +1228,7 @@ impl Func {
     /// // Note that this call can fail due to the typecheck not passing, but
     /// // in our case we statically know the module so we know this should
     /// // pass.
-    /// let typed = foo.typed::<(), (), _>(&store)?;
+    /// let typed = foo.typed::<(), ()>(&store)?;
     ///
     /// // Note that this can fail if the wasm traps at runtime.
     /// typed.call(&mut store, ())?;
@@ -1245,7 +1241,7 @@ impl Func {
     /// ```
     /// # use wasmtime::*;
     /// # fn foo(add: &Func, mut store: Store<()>) -> anyhow::Result<()> {
-    /// let typed = add.typed::<(i32, i64), f32, _>(&store)?;
+    /// let typed = add.typed::<(i32, i64), f32>(&store)?;
     /// assert_eq!(typed.call(&mut store, (1, 2))?, 3.0);
     /// # Ok(())
     /// # }
@@ -1256,18 +1252,20 @@ impl Func {
     /// ```
     /// # use wasmtime::*;
     /// # fn foo(add_with_overflow: &Func, mut store: Store<()>) -> anyhow::Result<()> {
-    /// let typed = add_with_overflow.typed::<(u32, u32), (u32, i32), _>(&store)?;
+    /// let typed = add_with_overflow.typed::<(u32, u32), (u32, i32)>(&store)?;
     /// let (result, overflow) = typed.call(&mut store, (u32::max_value(), 2))?;
     /// assert_eq!(result, 1);
     /// assert_eq!(overflow, 1);
     /// # Ok(())
     /// # }
     /// ```
-    pub fn typed<Params, Results, S>(&self, store: S) -> Result<TypedFunc<Params, Results>>
+    pub fn typed<Params, Results>(
+        &self,
+        store: impl AsContext,
+    ) -> Result<TypedFunc<Params, Results>>
     where
         Params: WasmParams,
         Results: WasmResults,
-        S: AsContext,
     {
         // Type-check that the params/results are all valid
         let ty = self.ty(store);
diff --git a/crates/wasmtime/src/instance.rs b/crates/wasmtime/src/instance.rs
index cb7897ad3..970fb030a 100644
--- a/crates/wasmtime/src/instance.rs
+++ b/crates/wasmtime/src/instance.rs
@@ -457,21 +457,20 @@ impl Instance {
     /// # Panics
     ///
     /// Panics if `store` does not own this instance.
-    pub fn get_typed_func<Params, Results, S>(
+    pub fn get_typed_func<Params, Results>(
         &self,
-        mut store: S,
+        mut store: impl AsContextMut,
         name: &str,
     ) -> Result<TypedFunc<Params, Results>>
     where
         Params: crate::WasmParams,
         Results: crate::WasmResults,
-        S: AsContextMut,
     {
         let f = self
             .get_export(store.as_context_mut(), name)
             .and_then(|f| f.into_func())
             .ok_or_else(|| anyhow!("failed to find function export `{}`", name))?;
-        Ok(f.typed::<Params, Results, _>(store)
+        Ok(f.typed::<Params, Results>(store)
             .with_context(|| format!("failed to convert function `{}` to given type", name))?)
     }
 
diff --git a/crates/wasmtime/src/lib.rs b/crates/wasmtime/src/lib.rs
index 1f0d0a9e0..a11bdd940 100644
--- a/crates/wasmtime/src/lib.rs
+++ b/crates/wasmtime/src/lib.rs
@@ -57,7 +57,7 @@
 //!     // afterwards we can fetch exports by name, as well as asserting the
 //!     // type signature of the function with `get_typed_func`.
 //!     let instance = Instance::new(&mut store, &module, &[host_hello.into()])?;
-//!     let hello = instance.get_typed_func::<(), (), _>(&mut store, "hello")?;
+//!     let hello = instance.get_typed_func::<(), ()>(&mut store, "hello")?;
 //!
 //!     // And finally we can call the wasm!
 //!     hello.call(&mut store, ())?;
@@ -168,7 +168,7 @@
 //!     // resolve the imports of the module using name-based resolution.
 //!     let mut store = Store::new(&engine, 0);
 //!     let instance = linker.instantiate(&mut store, &module)?;
-//!     let hello = instance.get_typed_func::<(), (), _>(&mut store, "hello")?;
+//!     let hello = instance.get_typed_func::<(), ()>(&mut store, "hello")?;
 //!     hello.call(&mut store, ())?;
 //!
 //!     Ok(())
@@ -373,7 +373,7 @@
 //!     "#,
 //! )?;
 //! let instance = Instance::new(&mut store, &module, &[log_str.into()])?;
-//! let foo = instance.get_typed_func::<(), (), _>(&mut store, "foo")?;
+//! let foo = instance.get_typed_func::<(), ()>(&mut store, "foo")?;
 //! foo.call(&mut store, ())?;
 //! # Ok(())
 //! # }
diff --git a/crates/wasmtime/src/linker.rs b/crates/wasmtime/src/linker.rs
index ba7890898..c1891ff80 100644
--- a/crates/wasmtime/src/linker.rs
+++ b/crates/wasmtime/src/linker.rs
@@ -643,7 +643,7 @@ impl<T> Linker<T> {
     /// let module = Module::new(&engine, wat)?;
     /// linker.module(&mut store, "commander", &module)?;
     /// let run = linker.get_default(&mut store, "")?
-    ///     .typed::<(), (), _>(&store)?
+    ///     .typed::<(), ()>(&store)?
     ///     .clone();
     /// run.call(&mut store, ())?;
     /// run.call(&mut store, ())?;
@@ -664,7 +664,7 @@ impl<T> Linker<T> {
     /// let module = Module::new(&engine, wat)?;
     /// linker.module(&mut store, "", &module)?;
     /// let run = linker.get(&mut store, "", "run").unwrap().into_func().unwrap();
-    /// let count = run.typed::<(), i32, _>(&store)?.call(&mut store, ())?;
+    /// let count = run.typed::<(), i32>(&store)?.call(&mut store, ())?;
     /// assert_eq!(count, 0, "a Command should get a fresh instance on each invocation");
     ///
     /// # Ok(())
@@ -727,7 +727,7 @@ impl<T> Linker<T> {
 
                 if let Some(export) = instance.get_export(&mut store, "_initialize") {
                     if let Extern::Func(func) = export {
-                        func.typed::<(), (), _>(&store)
+                        func.typed::<(), ()>(&store)
                             .and_then(|f| f.call(&mut store, ()).map_err(Into::into))
                             .context("calling the Reactor initialization function")?;
                     }
@@ -793,7 +793,7 @@ impl<T> Linker<T> {
                 if let Some(export) = instance.get_export(&mut store, "_initialize") {
                     if let Extern::Func(func) = export {
                         let func = func
-                            .typed::<(), (), _>(&store)
+                            .typed::<(), ()>(&store)
                             .context("loading the Reactor initialization function")?;
                         func.call_async(&mut store, ())
                             .await
diff --git a/crates/wasmtime/src/trap.rs b/crates/wasmtime/src/trap.rs
index 710b3fdd3..7b2631e9b 100644
--- a/crates/wasmtime/src/trap.rs
+++ b/crates/wasmtime/src/trap.rs
@@ -54,12 +54,12 @@ use wasmtime_jit::{demangle_function_name, demangle_function_name_or_index};
 /// let mut store = Store::new(&engine, ());
 /// let instance = Instance::new(&mut store, &module, &[])?;
 ///
-/// let trap = instance.get_typed_func::<(), (), _>(&mut store, "trap")?;
+/// let trap = instance.get_typed_func::<(), ()>(&mut store, "trap")?;
 /// let error = trap.call(&mut store, ()).unwrap_err();
 /// assert_eq!(*error.downcast_ref::<Trap>().unwrap(), Trap::UnreachableCodeReached);
 /// assert!(error.root_cause().is::<Trap>());
 ///
-/// let overflow = instance.get_typed_func::<(), (), _>(&mut store, "overflow")?;
+/// let overflow = instance.get_typed_func::<(), ()>(&mut store, "overflow")?;
 /// let error = overflow.call(&mut store, ()).unwrap_err();
 /// assert_eq!(*error.downcast_ref::<Trap>().unwrap(), Trap::StackOverflow);
 /// # Ok(())
@@ -266,7 +266,7 @@ impl std::error::Error for Trap {}
 /// )?;
 /// let mut store = Store::new(&engine, ());
 /// let instance = Instance::new(&mut store, &module, &[])?;
-/// let func = instance.get_typed_func::<(), (), _>(&mut store, "run")?;
+/// let func = instance.get_typed_func::<(), ()>(&mut store, "run")?;
 /// let error = func.call(&mut store, ()).unwrap_err();
 /// let bt = error.downcast_ref::<WasmBacktrace>().unwrap();
 /// let frames = bt.frames();
diff --git a/crates/wiggle/generate/src/codegen_settings.rs b/crates/wiggle/generate/src/codegen_settings.rs
index 616a12dd7..11391ed71 100644
--- a/crates/wiggle/generate/src/codegen_settings.rs
+++ b/crates/wiggle/generate/src/codegen_settings.rs
@@ -1,6 +1,6 @@
-use crate::config::{AsyncConf, ErrorConf, TracingConf};
+use crate::config::{AsyncConf, ErrorConf, ErrorConfField, TracingConf};
 use anyhow::{anyhow, Error};
-use proc_macro2::TokenStream;
+use proc_macro2::{Ident, TokenStream};
 use quote::quote;
 use std::collections::HashMap;
 use std::rc::Rc;
@@ -39,7 +39,7 @@ impl CodegenSettings {
 }
 
 pub struct ErrorTransform {
-    m: Vec<UserErrorType>,
+    m: Vec<ErrorType>,
 }
 
 impl ErrorTransform {
@@ -49,7 +49,13 @@ impl ErrorTransform {
     pub fn new(conf: &ErrorConf, doc: &Document) -> Result<Self, Error> {
         let mut richtype_identifiers = HashMap::new();
         let m = conf.iter().map(|(ident, field)|
-            if let Some(abi_type) = doc.typename(&Id::new(ident.to_string())) {
+            match field {
+                ErrorConfField::Trappable(field) => if let Some(abi_type) = doc.typename(&Id::new(ident.to_string())) {
+                    Ok(ErrorType::Generated(TrappableErrorType { abi_type, rich_type: field.rich_error.clone() }))
+                } else {
+                    Err(anyhow!("No witx typename \"{}\" found", ident.to_string()))
+                },
+                ErrorConfField::User(field) => if let Some(abi_type) = doc.typename(&Id::new(ident.to_string())) {
                     if let Some(ident) = field.rich_error.get_ident() {
                         if let Some(prior_def) = richtype_identifiers.insert(ident.clone(), field.err_loc.clone())
                          {
@@ -58,11 +64,11 @@ impl ErrorTransform {
                                     ident, prior_def
                                 ));
                         }
-                        Ok(UserErrorType {
+                        Ok(ErrorType::User(UserErrorType {
                             abi_type,
                             rich_type: field.rich_error.clone(),
                             method_fragment: ident.to_string()
-                        })
+                        }))
                     } else {
                         return Err(anyhow!(
                             "rich error type must be identifier for now - TODO add ability to provide a corresponding identifier: {:?}",
@@ -71,23 +77,52 @@ impl ErrorTransform {
                     }
                 }
                 else { Err(anyhow!("No witx typename \"{}\" found", ident.to_string())) }
+            }
         ).collect::<Result<Vec<_>, Error>>()?;
         Ok(Self { m })
     }
 
-    pub fn iter(&self) -> impl Iterator<Item = &UserErrorType> {
+    pub fn iter(&self) -> impl Iterator<Item = &ErrorType> {
         self.m.iter()
     }
 
-    pub fn for_abi_error(&self, tref: &TypeRef) -> Option<&UserErrorType> {
+    pub fn for_abi_error(&self, tref: &TypeRef) -> Option<&ErrorType> {
         match tref {
             TypeRef::Name(nt) => self.for_name(nt),
             TypeRef::Value { .. } => None,
         }
     }
 
-    pub fn for_name(&self, nt: &NamedType) -> Option<&UserErrorType> {
-        self.m.iter().find(|u| u.abi_type.name == nt.name)
+    pub fn for_name(&self, nt: &NamedType) -> Option<&ErrorType> {
+        self.m.iter().find(|e| e.abi_type().name == nt.name)
+    }
+}
+
+pub enum ErrorType {
+    User(UserErrorType),
+    Generated(TrappableErrorType),
+}
+impl ErrorType {
+    pub fn abi_type(&self) -> &NamedType {
+        match self {
+            Self::User(u) => &u.abi_type,
+            Self::Generated(r) => &r.abi_type,
+        }
+    }
+}
+
+pub struct TrappableErrorType {
+    abi_type: Rc<NamedType>,
+    rich_type: Ident,
+}
+
+impl TrappableErrorType {
+    pub fn abi_type(&self) -> TypeRef {
+        TypeRef::Name(self.abi_type.clone())
+    }
+    pub fn typename(&self) -> TokenStream {
+        let richtype = &self.rich_type;
+        quote!(#richtype)
     }
 }
 
diff --git a/crates/wiggle/generate/src/config.rs b/crates/wiggle/generate/src/config.rs
index 1eedc1840..2e42a93ef 100644
--- a/crates/wiggle/generate/src/config.rs
+++ b/crates/wiggle/generate/src/config.rs
@@ -27,6 +27,7 @@ mod kw {
     syn::custom_keyword!(wasmtime);
     syn::custom_keyword!(tracing);
     syn::custom_keyword!(disable_for);
+    syn::custom_keyword!(trappable);
 }
 
 #[derive(Debug, Clone)]
@@ -274,14 +275,14 @@ impl Parse for ErrorConf {
             content.parse_terminated(Parse::parse)?;
         let mut m = HashMap::new();
         for i in items {
-            match m.insert(i.abi_error.clone(), i.clone()) {
+            match m.insert(i.abi_error().clone(), i.clone()) {
                 None => {}
                 Some(prev_def) => {
                     return Err(Error::new(
-                        i.err_loc,
+                        *i.err_loc(),
                         format!(
                         "duplicate definition of rich error type for {:?}: previously defined at {:?}",
-                        i.abi_error, prev_def.err_loc,
+                        i.abi_error(), prev_def.err_loc(),
                     ),
                     ))
                 }
@@ -291,14 +292,67 @@ impl Parse for ErrorConf {
     }
 }
 
+#[derive(Debug, Clone)]
+pub enum ErrorConfField {
+    Trappable(TrappableErrorConfField),
+    User(UserErrorConfField),
+}
+impl ErrorConfField {
+    pub fn abi_error(&self) -> &Ident {
+        match self {
+            Self::Trappable(t) => &t.abi_error,
+            Self::User(u) => &u.abi_error,
+        }
+    }
+    pub fn err_loc(&self) -> &Span {
+        match self {
+            Self::Trappable(t) => &t.err_loc,
+            Self::User(u) => &u.err_loc,
+        }
+    }
+}
+
+impl Parse for ErrorConfField {
+    fn parse(input: ParseStream) -> Result<Self> {
+        let err_loc = input.span();
+        let abi_error = input.parse::<Ident>()?;
+        let _arrow: Token![=>] = input.parse()?;
+
+        let lookahead = input.lookahead1();
+        if lookahead.peek(kw::trappable) {
+            let _ = input.parse::<kw::trappable>()?;
+            let rich_error = input.parse()?;
+            Ok(ErrorConfField::Trappable(TrappableErrorConfField {
+                abi_error,
+                rich_error,
+                err_loc,
+            }))
+        } else {
+            let rich_error = input.parse::<syn::Path>()?;
+            Ok(ErrorConfField::User(UserErrorConfField {
+                abi_error,
+                rich_error,
+                err_loc,
+            }))
+        }
+    }
+}
+
+#[derive(Clone, Debug)]
+pub struct TrappableErrorConfField {
+    pub abi_error: Ident,
+    pub rich_error: Ident,
+    pub err_loc: Span,
+}
+
 #[derive(Clone)]
-pub struct ErrorConfField {
+pub struct UserErrorConfField {
     pub abi_error: Ident,
     pub rich_error: syn::Path,
     pub err_loc: Span,
 }
 
-impl std::fmt::Debug for ErrorConfField {
+impl std::fmt::Debug for UserErrorConfField {
     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
         f.debug_struct("ErrorConfField")
             .field("abi_error", &self.abi_error)
@@ -308,20 +362,6 @@ impl std::fmt::Debug for ErrorConfField {
     }
 }
 
-impl Parse for ErrorConfField {
-    fn parse(input: ParseStream) -> Result<Self> {
-        let err_loc = input.span();
-        let abi_error = input.parse::<Ident>()?;
-        let _arrow: Token![=>] = input.parse()?;
-        let rich_error = input.parse::<syn::Path>()?;
-        Ok(ErrorConfField {
-            abi_error,
-            rich_error,
-            err_loc,
-        })
-    }
-}
-
 #[derive(Clone, Default, Debug)]
 /// Modules and funcs that have async signatures
 pub struct AsyncConf {
diff --git a/crates/wiggle/generate/src/funcs.rs b/crates/wiggle/generate/src/funcs.rs
index bd1f1eebd..63177c3c7 100644
--- a/crates/wiggle/generate/src/funcs.rs
+++ b/crates/wiggle/generate/src/funcs.rs
@@ -1,7 +1,7 @@
-use crate::codegen_settings::CodegenSettings;
+use crate::codegen_settings::{CodegenSettings, ErrorType};
 use crate::lifetimes::anon_lifetime;
 use crate::module_trait::passed_by_reference;
-use crate::names::Names;
+use crate::names;
 use crate::types::WiggleType;
 use proc_macro2::{Ident, Span, TokenStream};
 use quote::quote;
@@ -9,54 +9,50 @@ use std::mem;
 use witx::Instruction;
 
 pub fn define_func(
-    names: &Names,
     module: &witx::Module,
     func: &witx::InterfaceFunc,
     settings: &CodegenSettings,
 ) -> TokenStream {
-    let (ts, _bounds) = _define_func(names, module, func, settings);
+    let (ts, _bounds) = _define_func(module, func, settings);
     ts
 }
 
 pub fn func_bounds(
-    names: &Names,
     module: &witx::Module,
     func: &witx::InterfaceFunc,
     settings: &CodegenSettings,
 ) -> Vec<Ident> {
-    let (_ts, bounds) = _define_func(names, module, func, settings);
+    let (_ts, bounds) = _define_func(module, func, settings);
     bounds
 }
 
 fn _define_func(
-    names: &Names,
     module: &witx::Module,
     func: &witx::InterfaceFunc,
     settings: &CodegenSettings,
 ) -> (TokenStream, Vec<Ident>) {
-    let rt = names.runtime_mod();
-    let ident = names.func(&func.name);
+    let ident = names::func(&func.name);
 
     let (wasm_params, wasm_results) = func.wasm_signature();
     let param_names = (0..wasm_params.len())
         .map(|i| Ident::new(&format!("arg{}", i), Span::call_site()))
         .collect::<Vec<_>>();
     let abi_params = wasm_params.iter().zip(&param_names).map(|(arg, name)| {
-        let wasm = names.wasm_type(*arg);
+        let wasm = names::wasm_type(*arg);
         quote!(#name : #wasm)
     });
 
     let abi_ret = match wasm_results.len() {
         0 => quote!(()),
         1 => {
-            let ty = names.wasm_type(wasm_results[0]);
+            let ty = names::wasm_type(wasm_results[0]);
             quote!(#ty)
         }
         _ => unimplemented!(),
     };
 
     let mut body = TokenStream::new();
-    let mut bounds = vec![names.trait_name(&module.name)];
+    let mut bounds = vec![names::trait_name(&module.name)];
     func.call_interface(
         &module.name,
         &mut Rust {
@@ -64,8 +60,6 @@ fn _define_func(
             params: &param_names,
             block_storage: Vec::new(),
             blocks: Vec::new(),
-            rt: &rt,
-            names,
             module,
             funcname: func.name.as_str(),
             settings,
@@ -76,8 +70,8 @@ fn _define_func(
     let mod_name = &module.name.as_str();
     let func_name = &func.name.as_str();
     let mk_span = quote!(
-        let _span = #rt::tracing::span!(
-            #rt::tracing::Level::TRACE,
+        let _span = wiggle::tracing::span!(
+            wiggle::tracing::Level::TRACE,
             "wiggle abi",
             module = #mod_name,
             function = #func_name
@@ -99,9 +93,9 @@ fn _define_func(
                 #[allow(unreachable_code)] // deals with warnings in noreturn functions
                 pub fn #ident(
                     ctx: &mut (impl #(#bounds)+*),
-                    memory: &dyn #rt::GuestMemory,
+                    memory: &dyn wiggle::GuestMemory,
                     #(#abi_params),*
-                ) -> #rt::anyhow::Result<#abi_ret> {
+                ) -> wiggle::anyhow::Result<#abi_ret> {
                     use std::convert::TryFrom as _;
                     #traced_body
                 }
@@ -111,7 +105,7 @@ fn _define_func(
     } else {
         let traced_body = if settings.tracing.enabled_for(&mod_name, &func_name) {
             quote!(
-                use #rt::tracing::Instrument as _;
+                use wiggle::tracing::Instrument as _;
                 #mk_span
                 async move {
                     #body
@@ -129,9 +123,9 @@ fn _define_func(
                 #[allow(unreachable_code)] // deals with warnings in noreturn functions
                 pub fn #ident<'a>(
                     ctx: &'a mut (impl #(#bounds)+*),
-                    memory: &'a dyn #rt::GuestMemory,
+                    memory: &'a dyn wiggle::GuestMemory,
                     #(#abi_params),*
-                ) -> impl std::future::Future<Output = #rt::anyhow::Result<#abi_ret>> + 'a {
+                ) -> impl std::future::Future<Output = wiggle::anyhow::Result<#abi_ret>> + 'a {
                     use std::convert::TryFrom as _;
                     #traced_body
                 }
@@ -146,8 +140,6 @@ struct Rust<'a> {
     params: &'a [Ident],
     block_storage: Vec<TokenStream>,
     blocks: Vec<TokenStream>,
-    rt: &'a TokenStream,
-    names: &'a Names,
     module: &'a witx::Module,
     funcname: &'a str,
     settings: &'a CodegenSettings,
@@ -196,17 +188,16 @@ impl witx::Bindgen for Rust<'_> {
         operands: &mut Vec<TokenStream>,
         results: &mut Vec<TokenStream>,
     ) {
-        let rt = self.rt;
         let wrap_err = |location: &str| {
             let modulename = self.module.name.as_str();
             let funcname = self.funcname;
             quote! {
                 |e| {
-                    #rt::GuestError::InFunc {
+                    wiggle::GuestError::InFunc {
                         modulename: #modulename,
                         funcname: #funcname,
                         location: #location,
-                        err: Box::new(#rt::GuestError::from(e)),
+                        err: Box::new(wiggle::GuestError::from(e)),
                     }
                 }
             }
@@ -226,9 +217,9 @@ impl witx::Bindgen for Rust<'_> {
 
             Instruction::PointerFromI32 { ty } | Instruction::ConstPointerFromI32 { ty } => {
                 let val = operands.pop().unwrap();
-                let pointee_type = self.names.type_ref(ty, anon_lifetime());
+                let pointee_type = names::type_ref(ty, anon_lifetime());
                 results.push(quote! {
-                    #rt::GuestPtr::<#pointee_type>::new(memory, #val as u32)
+                    wiggle::GuestPtr::<#pointee_type>::new(memory, #val as u32)
                 });
             }
 
@@ -238,12 +229,12 @@ impl witx::Bindgen for Rust<'_> {
                 let ty = match &**ty.type_() {
                     witx::Type::Builtin(witx::BuiltinType::Char) => quote!(str),
                     _ => {
-                        let ty = self.names.type_ref(ty, anon_lifetime());
+                        let ty = names::type_ref(ty, anon_lifetime());
                         quote!([#ty])
                     }
                 };
                 results.push(quote! {
-                    #rt::GuestPtr::<#ty>::new(memory, (#ptr as u32, #len as u32));
+                    wiggle::GuestPtr::<#ty>::new(memory, (#ptr as u32, #len as u32));
                 })
             }
 
@@ -252,7 +243,7 @@ impl witx::Bindgen for Rust<'_> {
                 // out, and afterwards we call the function with those bindings.
                 let mut args = Vec::new();
                 for (i, param) in func.params.iter().enumerate() {
-                    let name = self.names.func_param(&param.name);
+                    let name = names::func_param(&param.name);
                     let val = &operands[i];
                     self.src.extend(quote!(let #name = #val;));
                     if passed_by_reference(param.tref.type_()) {
@@ -271,21 +262,21 @@ impl witx::Bindgen for Rust<'_> {
                         .params
                         .iter()
                         .map(|param| {
-                            let name = self.names.func_param(&param.name);
+                            let name = names::func_param(&param.name);
                             if param.impls_display() {
-                                quote!( #name = #rt::tracing::field::display(&#name) )
+                                quote!( #name = wiggle::tracing::field::display(&#name) )
                             } else {
-                                quote!( #name = #rt::tracing::field::debug(&#name) )
+                                quote!( #name = wiggle::tracing::field::debug(&#name) )
                             }
                         })
                         .collect::<Vec<_>>();
                     self.src.extend(quote! {
-                        #rt::tracing::event!(#rt::tracing::Level::TRACE, #(#args),*);
+                        wiggle::tracing::event!(wiggle::tracing::Level::TRACE, #(#args),*);
                     });
                 }
 
-                let trait_name = self.names.trait_name(&self.module.name);
-                let ident = self.names.func(&func.name);
+                let trait_name = names::trait_name(&self.module.name);
+                let ident = names::func(&func.name);
                 if self.settings.get_async(&self.module, &func).is_sync() {
                     self.src.extend(quote! {
                         let ret = #trait_name::#ident(ctx, #(#args),*);
@@ -301,9 +292,9 @@ impl witx::Bindgen for Rust<'_> {
                     .enabled_for(self.module.name.as_str(), self.funcname)
                 {
                     self.src.extend(quote! {
-                        #rt::tracing::event!(
-                            #rt::tracing::Level::TRACE,
-                            result = #rt::tracing::field::debug(&ret),
+                        wiggle::tracing::event!(
+                            wiggle::tracing::Level::TRACE,
+                            result = wiggle::tracing::field::debug(&ret),
                         );
                     });
                 }
@@ -322,11 +313,12 @@ impl witx::Bindgen for Rust<'_> {
             Instruction::EnumLower { ty } => {
                 let val = operands.pop().unwrap();
                 let val = match self.settings.errors.for_name(ty) {
-                    Some(custom) => {
-                        let method = self.names.user_error_conversion_method(&custom);
+                    Some(ErrorType::User(custom)) => {
+                        let method = names::user_error_conversion_method(&custom);
                         self.bound(quote::format_ident!("UserErrorConversion"));
                         quote!(UserErrorConversion::#method(ctx, #val)?)
                     }
+                    Some(ErrorType::Generated(_)) => quote!(#val.downcast()?),
                     None => val,
                 };
                 results.push(quote!(#val as i32));
@@ -336,10 +328,10 @@ impl witx::Bindgen for Rust<'_> {
                 let err = self.blocks.pop().unwrap();
                 let ok = self.blocks.pop().unwrap();
                 let val = operands.pop().unwrap();
-                let err_typename = self.names.type_ref(err_ty.unwrap(), anon_lifetime());
+                let err_typename = names::type_ref(err_ty.unwrap(), anon_lifetime());
                 results.push(quote! {
                     match #val {
-                        Ok(e) => { #ok; <#err_typename as #rt::GuestErrorType>::success() as i32 }
+                        Ok(e) => { #ok; <#err_typename as wiggle::GuestErrorType>::success() as i32 }
                         Err(e) => { #err }
                     }
                 });
@@ -369,9 +361,9 @@ impl witx::Bindgen for Rust<'_> {
                 let ptr = operands.pop().unwrap();
                 let val = operands.pop().unwrap();
                 let wrap_err = wrap_err(&format!("write {}", ty.name.as_str()));
-                let pointee_type = self.names.type_(&ty.name);
+                let pointee_type = names::type_(&ty.name);
                 self.src.extend(quote! {
-                    #rt::GuestPtr::<#pointee_type>::new(memory, #ptr as u32)
+                    wiggle::GuestPtr::<#pointee_type>::new(memory, #ptr as u32)
                         .write(#val)
                         .map_err(#wrap_err)?;
                 });
@@ -380,9 +372,9 @@ impl witx::Bindgen for Rust<'_> {
             Instruction::Load { ty } => {
                 let ptr = operands.pop().unwrap();
                 let wrap_err = wrap_err(&format!("read {}", ty.name.as_str()));
-                let pointee_type = self.names.type_(&ty.name);
+                let pointee_type = names::type_(&ty.name);
                 results.push(quote! {
-                    #rt::GuestPtr::<#pointee_type>::new(memory, #ptr as u32)
+                    wiggle::GuestPtr::<#pointee_type>::new(memory, #ptr as u32)
                         .read()
                         .map_err(#wrap_err)?
                 });
@@ -390,7 +382,7 @@ impl witx::Bindgen for Rust<'_> {
 
             Instruction::HandleFromI32 { ty } => {
                 let val = operands.pop().unwrap();
-                let ty = self.names.type_(&ty.name);
+                let ty = names::type_(&ty.name);
                 results.push(quote!(#ty::from(#val)));
             }
 
@@ -418,7 +410,7 @@ impl witx::Bindgen for Rust<'_> {
             Instruction::EnumLift { ty }
             | Instruction::BitflagsFromI64 { ty }
             | Instruction::BitflagsFromI32 { ty } => {
-                let ty = self.names.type_(&ty.name);
+                let ty = names::type_(&ty.name);
                 try_from(quote!(#ty))
             }
 
diff --git a/crates/wiggle/generate/src/lib.rs b/crates/wiggle/generate/src/lib.rs
index 9b5cea757..77bbe6b00 100644
--- a/crates/wiggle/generate/src/lib.rs
+++ b/crates/wiggle/generate/src/lib.rs
@@ -3,7 +3,7 @@ pub mod config;
 mod funcs;
 mod lifetimes;
 mod module_trait;
-mod names;
+pub mod names;
 mod types;
 pub mod wasmtime;
 
@@ -12,19 +12,16 @@ use lifetimes::anon_lifetime;
 use proc_macro2::{Literal, TokenStream};
 use quote::quote;
 
-pub use codegen_settings::{CodegenSettings, UserErrorType};
+pub use codegen_settings::{CodegenSettings, ErrorType, UserErrorType};
 pub use config::{Config, WasmtimeConfig};
 pub use funcs::define_func;
 pub use module_trait::define_module_trait;
-pub use names::Names;
 pub use types::define_datatype;
 
-pub fn generate(doc: &witx::Document, names: &Names, settings: &CodegenSettings) -> TokenStream {
-    // TODO at some point config should grow more ability to configure name
-    // overrides.
-    let rt = names.runtime_mod();
-
-    let types = doc.typenames().map(|t| define_datatype(&names, &t));
+pub fn generate(doc: &witx::Document, settings: &CodegenSettings) -> TokenStream {
+    let types = doc
+        .typenames()
+        .map(|t| define_datatype(&t, settings.errors.for_name(&t)));
 
     let constants = doc.constants().map(|c| {
         let name = quote::format_ident!(
@@ -32,18 +29,24 @@ pub fn generate(doc: &witx::Document, names: &Names, settings: &CodegenSettings)
             c.ty.as_str().to_shouty_snake_case(),
             c.name.as_str().to_shouty_snake_case()
         );
-        let ty = names.type_(&c.ty);
+        let ty = names::type_(&c.ty);
         let value = Literal::u64_unsuffixed(c.value);
         quote! {
             pub const #name: #ty = #value;
         }
     });
 
-    let user_error_methods = settings.errors.iter().map(|errtype| {
-        let abi_typename = names.type_ref(&errtype.abi_type(), anon_lifetime());
-        let user_typename = errtype.typename();
-        let methodname = names.user_error_conversion_method(&errtype);
-        quote!(fn #methodname(&mut self, e: super::#user_typename) -> #rt::anyhow::Result<#abi_typename>;)
+    let user_error_methods = settings.errors.iter().filter_map(|errtype| match errtype {
+        ErrorType::User(errtype) => {
+            let abi_typename = names::type_ref(&errtype.abi_type(), anon_lifetime());
+            let user_typename = errtype.typename();
+            let methodname = names::user_error_conversion_method(&errtype);
+            Some(quote! {
+                fn #methodname(&mut self, e: super::#user_typename)
+                    -> wiggle::anyhow::Result<#abi_typename>;
+            })
+        }
+        ErrorType::Generated(_) => None,
     });
     let user_error_conversion = quote! {
         pub trait UserErrorConversion {
@@ -51,13 +54,11 @@ pub fn generate(doc: &witx::Document, names: &Names, settings: &CodegenSettings)
         }
     };
     let modules = doc.modules().map(|module| {
-        let modname = names.module(&module.name);
-        let fs = module
-            .funcs()
-            .map(|f| define_func(&names, &module, &f, &settings));
-        let modtrait = define_module_trait(&names, &module, &settings);
+        let modname = names::module(&module.name);
+        let fs = module.funcs().map(|f| define_func(&module, &f, &settings));
+        let modtrait = define_module_trait(&module, &settings);
         let wasmtime = if settings.wasmtime {
-            crate::wasmtime::link_module(&module, &names, None, &settings)
+            crate::wasmtime::link_module(&module, None, &settings)
         } else {
             quote! {}
         };
@@ -86,14 +87,13 @@ pub fn generate(doc: &witx::Document, names: &Names, settings: &CodegenSettings)
     )
 }
 
-pub fn generate_metadata(doc: &witx::Document, names: &Names) -> TokenStream {
-    let rt = names.runtime_mod();
+pub fn generate_metadata(doc: &witx::Document) -> TokenStream {
     let doc_text = &format!("{}", doc);
     quote! {
         pub mod metadata {
             pub const DOC_TEXT: &str = #doc_text;
-            pub fn document() -> #rt::witx::Document {
-                #rt::witx::parse(DOC_TEXT).unwrap()
+            pub fn document() -> wiggle::witx::Document {
+                wiggle::witx::parse(DOC_TEXT).unwrap()
             }
         }
     }
diff --git a/crates/wiggle/generate/src/module_trait.rs b/crates/wiggle/generate/src/module_trait.rs
index be9a47ad3..c1dbfac21 100644
--- a/crates/wiggle/generate/src/module_trait.rs
+++ b/crates/wiggle/generate/src/module_trait.rs
@@ -1,9 +1,9 @@
 use proc_macro2::TokenStream;
 use quote::quote;
 
-use crate::codegen_settings::CodegenSettings;
+use crate::codegen_settings::{CodegenSettings, ErrorType};
 use crate::lifetimes::{anon_lifetime, LifetimeExt};
-use crate::names::Names;
+use crate::names;
 use witx::Module;
 
 pub fn passed_by_reference(ty: &witx::Type) -> bool {
@@ -15,9 +15,8 @@ pub fn passed_by_reference(ty: &witx::Type) -> bool {
     }
 }
 
-pub fn define_module_trait(names: &Names, m: &Module, settings: &CodegenSettings) -> TokenStream {
-    let traitname = names.trait_name(&m.name);
-    let rt = names.runtime_mod();
+pub fn define_module_trait(m: &Module, settings: &CodegenSettings) -> TokenStream {
+    let traitname = names::trait_name(&m.name);
     let traitmethods = m.funcs().map(|f| {
         // Check if we're returning an entity anotated with a lifetime,
         // in which case, we'll need to annotate the function itself, and
@@ -32,10 +31,10 @@ pub fn define_module_trait(names: &Names, m: &Module, settings: &CodegenSettings
         } else {
             (anon_lifetime(), true)
         };
-        let funcname = names.func(&f.name);
+        let funcname = names::func(&f.name);
         let args = f.params.iter().map(|arg| {
-            let arg_name = names.func_param(&arg.name);
-            let arg_typename = names.type_ref(&arg.tref, lifetime.clone());
+            let arg_name = names::func_param(&arg.name);
+            let arg_typename = names::type_ref(&arg.tref, lifetime.clone());
             let arg_type = if passed_by_reference(&*arg.tref.type_()) {
                 quote!(&#arg_typename)
             } else {
@@ -45,7 +44,7 @@ pub fn define_module_trait(names: &Names, m: &Module, settings: &CodegenSettings
         });
 
         let result = match f.results.len() {
-            0 if f.noreturn => quote!(#rt::anyhow::Error),
+            0 if f.noreturn => quote!(wiggle::anyhow::Error),
             0 => quote!(()),
             1 => {
                 let (ok, err) = match &**f.results[0].tref.type_() {
@@ -57,16 +56,17 @@ pub fn define_module_trait(names: &Names, m: &Module, settings: &CodegenSettings
                 };
 
                 let ok = match ok {
-                    Some(ty) => names.type_ref(ty, lifetime.clone()),
+                    Some(ty) => names::type_ref(ty, lifetime.clone()),
                     None => quote!(()),
                 };
                 let err = match err {
                     Some(ty) => match settings.errors.for_abi_error(ty) {
-                        Some(custom) => {
+                        Some(ErrorType::User(custom)) => {
                             let tn = custom.typename();
                             quote!(super::#tn)
                         }
-                        None => names.type_ref(ty, lifetime.clone()),
+                        Some(ErrorType::Generated(g)) => g.typename(),
+                        None => names::type_ref(ty, lifetime.clone()),
                     },
                     None => quote!(()),
                 };
@@ -89,7 +89,7 @@ pub fn define_module_trait(names: &Names, m: &Module, settings: &CodegenSettings
     });
 
     quote! {
-        #[#rt::async_trait]
+        #[wiggle::async_trait]
         pub trait #traitname {
             #(#traitmethods)*
         }
diff --git a/crates/wiggle/generate/src/names.rs b/crates/wiggle/generate/src/names.rs
index 635ba735a..a32a35be5 100644
--- a/crates/wiggle/generate/src/names.rs
+++ b/crates/wiggle/generate/src/names.rs
@@ -6,204 +6,186 @@ use witx::{BuiltinType, Id, Type, TypeRef, WasmType};
 
 use crate::{lifetimes::LifetimeExt, UserErrorType};
 
-pub struct Names {
-    runtime_mod: TokenStream,
+pub fn type_(id: &Id) -> Ident {
+    escape_id(id, NamingConvention::CamelCase)
 }
 
-impl Names {
-    pub fn new(runtime_mod: TokenStream) -> Names {
-        Names { runtime_mod }
-    }
-
-    pub fn runtime_mod(&self) -> TokenStream {
-        self.runtime_mod.clone()
+pub fn builtin_type(b: BuiltinType) -> TokenStream {
+    match b {
+        BuiltinType::U8 { .. } => quote!(u8),
+        BuiltinType::U16 => quote!(u16),
+        BuiltinType::U32 { .. } => quote!(u32),
+        BuiltinType::U64 => quote!(u64),
+        BuiltinType::S8 => quote!(i8),
+        BuiltinType::S16 => quote!(i16),
+        BuiltinType::S32 => quote!(i32),
+        BuiltinType::S64 => quote!(i64),
+        BuiltinType::F32 => quote!(f32),
+        BuiltinType::F64 => quote!(f64),
+        BuiltinType::Char => quote!(char),
     }
+}
 
-    pub fn type_(&self, id: &Id) -> TokenStream {
-        let ident = escape_id(id, NamingConvention::CamelCase);
-        quote!(#ident)
-    }
-
-    pub fn builtin_type(&self, b: BuiltinType) -> TokenStream {
-        match b {
-            BuiltinType::U8 { .. } => quote!(u8),
-            BuiltinType::U16 => quote!(u16),
-            BuiltinType::U32 { .. } => quote!(u32),
-            BuiltinType::U64 => quote!(u64),
-            BuiltinType::S8 => quote!(i8),
-            BuiltinType::S16 => quote!(i16),
-            BuiltinType::S32 => quote!(i32),
-            BuiltinType::S64 => quote!(i64),
-            BuiltinType::F32 => quote!(f32),
-            BuiltinType::F64 => quote!(f64),
-            BuiltinType::Char => quote!(char),
-        }
+pub fn wasm_type(ty: WasmType) -> TokenStream {
+    match ty {
+        WasmType::I32 => quote!(i32),
+        WasmType::I64 => quote!(i64),
+        WasmType::F32 => quote!(f32),
+        WasmType::F64 => quote!(f64),
     }
+}
 
-    pub fn wasm_type(&self, ty: WasmType) -> TokenStream {
-        match ty {
-            WasmType::I32 => quote!(i32),
-            WasmType::I64 => quote!(i64),
-            WasmType::F32 => quote!(f32),
-            WasmType::F64 => quote!(f64),
+pub fn type_ref(tref: &TypeRef, lifetime: TokenStream) -> TokenStream {
+    match tref {
+        TypeRef::Name(nt) => {
+            let ident = type_(&nt.name);
+            if nt.tref.needs_lifetime() {
+                quote!(#ident<#lifetime>)
+            } else {
+                quote!(#ident)
+            }
         }
-    }
-
-    pub fn type_ref(&self, tref: &TypeRef, lifetime: TokenStream) -> TokenStream {
-        match tref {
-            TypeRef::Name(nt) => {
-                let ident = self.type_(&nt.name);
-                if nt.tref.needs_lifetime() {
-                    quote!(#ident<#lifetime>)
-                } else {
-                    quote!(#ident)
-                }
+        TypeRef::Value(ty) => match &**ty {
+            Type::Builtin(builtin) => builtin_type(*builtin),
+            Type::Pointer(pointee) | Type::ConstPointer(pointee) => {
+                let pointee_type = type_ref(&pointee, lifetime.clone());
+                quote!(wiggle::GuestPtr<#lifetime, #pointee_type>)
             }
-            TypeRef::Value(ty) => match &**ty {
-                Type::Builtin(builtin) => self.builtin_type(*builtin),
-                Type::Pointer(pointee) | Type::ConstPointer(pointee) => {
-                    let rt = self.runtime_mod();
-                    let pointee_type = self.type_ref(&pointee, lifetime.clone());
-                    quote!(#rt::GuestPtr<#lifetime, #pointee_type>)
+            Type::List(pointee) => match &**pointee.type_() {
+                Type::Builtin(BuiltinType::Char) => {
+                    quote!(wiggle::GuestPtr<#lifetime, str>)
                 }
-                Type::List(pointee) => match &**pointee.type_() {
-                    Type::Builtin(BuiltinType::Char) => {
-                        let rt = self.runtime_mod();
-                        quote!(#rt::GuestPtr<#lifetime, str>)
-                    }
-                    _ => {
-                        let rt = self.runtime_mod();
-                        let pointee_type = self.type_ref(&pointee, lifetime.clone());
-                        quote!(#rt::GuestPtr<#lifetime, [#pointee_type]>)
-                    }
-                },
-                Type::Variant(v) => match v.as_expected() {
-                    Some((ok, err)) => {
-                        let ok = match ok {
-                            Some(ty) => self.type_ref(ty, lifetime.clone()),
-                            None => quote!(()),
-                        };
-                        let err = match err {
-                            Some(ty) => self.type_ref(ty, lifetime.clone()),
-                            None => quote!(()),
-                        };
-                        quote!(Result<#ok, #err>)
-                    }
-                    None => unimplemented!("anonymous variant ref {:?}", tref),
-                },
-                Type::Record(r) if r.is_tuple() => {
-                    let types = r
-                        .members
-                        .iter()
-                        .map(|m| self.type_ref(&m.tref, lifetime.clone()))
-                        .collect::<Vec<_>>();
-                    quote!((#(#types,)*))
+                _ => {
+                    let pointee_type = type_ref(&pointee, lifetime.clone());
+                    quote!(wiggle::GuestPtr<#lifetime, [#pointee_type]>)
                 }
-                _ => unimplemented!("anonymous type ref {:?}", tref),
             },
-        }
+            Type::Variant(v) => match v.as_expected() {
+                Some((ok, err)) => {
+                    let ok = match ok {
+                        Some(ty) => type_ref(ty, lifetime.clone()),
+                        None => quote!(()),
+                    };
+                    let err = match err {
+                        Some(ty) => type_ref(ty, lifetime.clone()),
+                        None => quote!(()),
+                    };
+                    quote!(Result<#ok, #err>)
+                }
+                None => unimplemented!("anonymous variant ref {:?}", tref),
+            },
+            Type::Record(r) if r.is_tuple() => {
+                let types = r
+                    .members
+                    .iter()
+                    .map(|m| type_ref(&m.tref, lifetime.clone()))
+                    .collect::<Vec<_>>();
+                quote!((#(#types,)*))
+            }
+            _ => unimplemented!("anonymous type ref {:?}", tref),
+        },
     }
+}
 
-    /// Convert an enum variant from its [`Id`][witx] name to its Rust [`Ident`][id] representation.
-    ///
-    /// [id]: https://docs.rs/proc-macro2/*/proc_macro2/struct.Ident.html
-    /// [witx]: https://docs.rs/witx/*/witx/struct.Id.html
-    pub fn enum_variant(&self, id: &Id) -> Ident {
-        handle_2big_enum_variant(id).unwrap_or_else(|| escape_id(id, NamingConvention::CamelCase))
-    }
+/// Convert an enum variant from its [`Id`][witx] name to its Rust [`Ident`][id] representation.
+///
+/// [id]: https://docs.rs/proc-macro2/*/proc_macro2/struct.Ident.html
+/// [witx]: https://docs.rs/witx/*/witx/struct.Id.html
+pub fn enum_variant(id: &Id) -> Ident {
+    handle_2big_enum_variant(id).unwrap_or_else(|| escape_id(id, NamingConvention::CamelCase))
+}
 
-    pub fn flag_member(&self, id: &Id) -> Ident {
-        format_ident!("{}", id.as_str().to_shouty_snake_case())
-    }
+pub fn flag_member(id: &Id) -> Ident {
+    format_ident!("{}", id.as_str().to_shouty_snake_case())
+}
 
-    pub fn int_member(&self, id: &Id) -> Ident {
-        format_ident!("{}", id.as_str().to_shouty_snake_case())
-    }
+pub fn int_member(id: &Id) -> Ident {
+    format_ident!("{}", id.as_str().to_shouty_snake_case())
+}
 
-    /// Convert a struct member from its [`Id`][witx] name to its Rust [`Ident`][id] representation.
-    ///
-    /// [id]: https://docs.rs/proc-macro2/*/proc_macro2/struct.Ident.html
-    /// [witx]: https://docs.rs/witx/*/witx/struct.Id.html
-    pub fn struct_member(&self, id: &Id) -> Ident {
-        escape_id(id, NamingConvention::SnakeCase)
-    }
+/// Convert a struct member from its [`Id`][witx] name to its Rust [`Ident`][id] representation.
+///
+/// [id]: https://docs.rs/proc-macro2/*/proc_macro2/struct.Ident.html
+/// [witx]: https://docs.rs/witx/*/witx/struct.Id.html
+pub fn struct_member(id: &Id) -> Ident {
+    escape_id(id, NamingConvention::SnakeCase)
+}
 
-    /// Convert a module name from its [`Id`][witx] name to its Rust [`Ident`][id] representation.
-    ///
-    /// [id]: https://docs.rs/proc-macro2/*/proc_macro2/struct.Ident.html
-    /// [witx]: https://docs.rs/witx/*/witx/struct.Id.html
-    pub fn module(&self, id: &Id) -> Ident {
-        escape_id(id, NamingConvention::SnakeCase)
-    }
+/// Convert a module name from its [`Id`][witx] name to its Rust [`Ident`][id] representation.
+///
+/// [id]: https://docs.rs/proc-macro2/*/proc_macro2/struct.Ident.html
+/// [witx]: https://docs.rs/witx/*/witx/struct.Id.html
+pub fn module(id: &Id) -> Ident {
+    escape_id(id, NamingConvention::SnakeCase)
+}
 
-    /// Convert a trait name from its [`Id`][witx] name to its Rust [`Ident`][id] representation.
-    ///
-    /// [id]: https://docs.rs/proc-macro2/*/proc_macro2/struct.Ident.html
-    /// [witx]: https://docs.rs/witx/*/witx/struct.Id.html
-    pub fn trait_name(&self, id: &Id) -> Ident {
-        escape_id(id, NamingConvention::CamelCase)
-    }
+/// Convert a trait name from its [`Id`][witx] name to its Rust [`Ident`][id] representation.
+///
+/// [id]: https://docs.rs/proc-macro2/*/proc_macro2/struct.Ident.html
+/// [witx]: https://docs.rs/witx/*/witx/struct.Id.html
+pub fn trait_name(id: &Id) -> Ident {
+    escape_id(id, NamingConvention::CamelCase)
+}
 
-    /// Convert a function name from its [`Id`][witx] name to its Rust [`Ident`][id] representation.
-    ///
-    /// [id]: https://docs.rs/proc-macro2/*/proc_macro2/struct.Ident.html
-    /// [witx]: https://docs.rs/witx/*/witx/struct.Id.html
-    pub fn func(&self, id: &Id) -> Ident {
-        escape_id(id, NamingConvention::SnakeCase)
-    }
+/// Convert a function name from its [`Id`][witx] name to its Rust [`Ident`][id] representation.
+///
+/// [id]: https://docs.rs/proc-macro2/*/proc_macro2/struct.Ident.html
+/// [witx]: https://docs.rs/witx/*/witx/struct.Id.html
+pub fn func(id: &Id) -> Ident {
+    escape_id(id, NamingConvention::SnakeCase)
+}
 
-    /// Convert a parameter name from its [`Id`][witx] name to its Rust [`Ident`][id] representation.
-    ///
-    /// [id]: https://docs.rs/proc-macro2/*/proc_macro2/struct.Ident.html
-    /// [witx]: https://docs.rs/witx/*/witx/struct.Id.html
-    pub fn func_param(&self, id: &Id) -> Ident {
-        escape_id(id, NamingConvention::SnakeCase)
-    }
+/// Convert a parameter name from its [`Id`][witx] name to its Rust [`Ident`][id] representation.
+///
+/// [id]: https://docs.rs/proc-macro2/*/proc_macro2/struct.Ident.html
+/// [witx]: https://docs.rs/witx/*/witx/struct.Id.html
+pub fn func_param(id: &Id) -> Ident {
+    escape_id(id, NamingConvention::SnakeCase)
+}
 
-    /// For when you need a {name}_ptr binding for passing a value by reference:
-    pub fn func_ptr_binding(&self, id: &Id) -> Ident {
-        format_ident!("{}_ptr", id.as_str().to_snake_case())
-    }
+/// For when you need a {name}_ptr binding for passing a value by reference:
+pub fn func_ptr_binding(id: &Id) -> Ident {
+    format_ident!("{}_ptr", id.as_str().to_snake_case())
+}
 
-    /// For when you need a {name}_len binding for passing an array:
-    pub fn func_len_binding(&self, id: &Id) -> Ident {
-        format_ident!("{}_len", id.as_str().to_snake_case())
-    }
+/// For when you need a {name}_len binding for passing an array:
+pub fn func_len_binding(id: &Id) -> Ident {
+    format_ident!("{}_len", id.as_str().to_snake_case())
+}
 
-    fn builtin_name(b: &BuiltinType) -> &'static str {
-        match b {
-            BuiltinType::U8 { .. } => "u8",
-            BuiltinType::U16 => "u16",
-            BuiltinType::U32 { .. } => "u32",
-            BuiltinType::U64 => "u64",
-            BuiltinType::S8 => "i8",
-            BuiltinType::S16 => "i16",
-            BuiltinType::S32 => "i32",
-            BuiltinType::S64 => "i64",
-            BuiltinType::F32 => "f32",
-            BuiltinType::F64 => "f64",
-            BuiltinType::Char => "char",
-        }
+fn builtin_name(b: &BuiltinType) -> &'static str {
+    match b {
+        BuiltinType::U8 { .. } => "u8",
+        BuiltinType::U16 => "u16",
+        BuiltinType::U32 { .. } => "u32",
+        BuiltinType::U64 => "u64",
+        BuiltinType::S8 => "i8",
+        BuiltinType::S16 => "i16",
+        BuiltinType::S32 => "i32",
+        BuiltinType::S64 => "i64",
+        BuiltinType::F32 => "f32",
+        BuiltinType::F64 => "f64",
+        BuiltinType::Char => "char",
     }
+}
 
-    fn snake_typename(tref: &TypeRef) -> String {
-        match tref {
-            TypeRef::Name(nt) => nt.name.as_str().to_snake_case(),
-            TypeRef::Value(ty) => match &**ty {
-                Type::Builtin(b) => Self::builtin_name(&b).to_owned(),
-                _ => panic!("unexpected anonymous type: {:?}", ty),
-            },
-        }
+fn snake_typename(tref: &TypeRef) -> String {
+    match tref {
+        TypeRef::Name(nt) => nt.name.as_str().to_snake_case(),
+        TypeRef::Value(ty) => match &**ty {
+            Type::Builtin(b) => builtin_name(&b).to_owned(),
+            _ => panic!("unexpected anonymous type: {:?}", ty),
+        },
     }
+}
 
-    pub fn user_error_conversion_method(&self, user_type: &UserErrorType) -> Ident {
-        let abi_type = Self::snake_typename(&user_type.abi_type());
-        format_ident!(
-            "{}_from_{}",
-            abi_type,
-            user_type.method_fragment().to_snake_case()
-        )
-    }
+pub fn user_error_conversion_method(user_type: &UserErrorType) -> Ident {
+    let abi_type = snake_typename(&user_type.abi_type());
+    format_ident!(
+        "{}_from_{}",
+        abi_type,
+        user_type.method_fragment().to_snake_case()
+    )
 }
 
 /// Identifier escaping utilities.
diff --git a/crates/wiggle/generate/src/types/error.rs b/crates/wiggle/generate/src/types/error.rs
new file mode 100644
index 000000000..9f0d51f7a
--- /dev/null
+++ b/crates/wiggle/generate/src/types/error.rs
@@ -0,0 +1,53 @@
+use crate::codegen_settings::TrappableErrorType;
+use crate::names;
+
+use proc_macro2::TokenStream;
+use quote::quote;
+
+pub(super) fn define_error(
+    name: &witx::Id,
+    _v: &witx::Variant,
+    e: &TrappableErrorType,
+) -> TokenStream {
+    let abi_error = names::type_(name);
+    let rich_error = e.typename();
+
+    quote! {
+        #[derive(Debug)]
+        pub struct #rich_error {
+            inner: anyhow::Error,
+        }
+
+        impl std::fmt::Display for #rich_error {
+            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+                write!(f, "{}", self.inner)
+            }
+        }
+        impl std::error::Error for #rich_error {
+            fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
+                self.inner.source()
+            }
+        }
+
+        impl #rich_error {
+            pub fn trap(inner: anyhow::Error) -> #rich_error {
+                Self { inner }
+            }
+            pub fn downcast(self) -> Result<#abi_error, anyhow::Error> {
+                self.inner.downcast()
+            }
+            pub fn downcast_ref(&self) -> Option<&#abi_error> {
+                self.inner.downcast_ref()
+            }
+            pub fn context(self, s: impl Into<String>) -> Self {
+                Self { inner: self.inner.context(s.into()) }
+            }
+        }
+
+        impl From<#abi_error> for #rich_error {
+            fn from(abi: #abi_error) -> #rich_error {
+                #rich_error { inner: anyhow::Error::from(abi) }
+            }
+        }
+    }
+}
diff --git a/crates/wiggle/generate/src/types/flags.rs b/crates/wiggle/generate/src/types/flags.rs
index 34e35eeb2..a55d3f002 100644
--- a/crates/wiggle/generate/src/types/flags.rs
+++ b/crates/wiggle/generate/src/types/flags.rs
@@ -1,30 +1,28 @@
-use crate::names::Names;
+use crate::names;
 
 use proc_macro2::{Literal, TokenStream};
 use quote::quote;
 
 pub(super) fn define_flags(
-    names: &Names,
     name: &witx::Id,
     repr: witx::IntRepr,
     record: &witx::RecordDatatype,
 ) -> TokenStream {
-    let rt = names.runtime_mod();
-    let ident = names.type_(&name);
-    let abi_repr = names.wasm_type(repr.into());
+    let ident = names::type_(&name);
+    let abi_repr = names::wasm_type(repr.into());
     let repr = super::int_repr_tokens(repr);
 
     let mut names_ = vec![];
     let mut values_ = vec![];
     for (i, member) in record.members.iter().enumerate() {
-        let name = names.flag_member(&member.name);
+        let name = names::flag_member(&member.name);
         let value_token = Literal::usize_unsuffixed(1 << i);
         names_.push(name);
         values_.push(value_token);
     }
 
     quote! {
-        #rt::bitflags::bitflags! {
+        wiggle::bitflags::bitflags! {
             pub struct #ident: #repr {
                 #(const #names_ = #values_;)*
             }
@@ -43,10 +41,10 @@ pub(super) fn define_flags(
         }
 
         impl TryFrom<#repr> for #ident {
-            type Error = #rt::GuestError;
-            fn try_from(value: #repr) -> Result<Self, #rt::GuestError> {
+            type Error = wiggle::GuestError;
+            fn try_from(value: #repr) -> Result<Self, wiggle::GuestError> {
                 if #repr::from(!#ident::all()) & value != 0 {
-                    Err(#rt::GuestError::InvalidFlagValue(stringify!(#ident)))
+                    Err(wiggle::GuestError::InvalidFlagValue(stringify!(#ident)))
                 } else {
                     Ok(#ident { bits: value })
                 }
@@ -54,8 +52,8 @@ pub(super) fn define_flags(
         }
 
         impl TryFrom<#abi_repr> for #ident {
-            type Error = #rt::GuestError;
-            fn try_from(value: #abi_repr) -> Result<Self, #rt::GuestError> {
+            type Error = wiggle::GuestError;
+            fn try_from(value: #abi_repr) -> Result<Self, wiggle::GuestError> {
                 #ident::try_from(#repr::try_from(value)?)
             }
         }
@@ -66,7 +64,7 @@ pub(super) fn define_flags(
             }
         }
 
-        impl<'a> #rt::GuestType<'a> for #ident {
+        impl<'a> wiggle::GuestType<'a> for #ident {
             fn guest_size() -> u32 {
                 #repr::guest_size()
             }
@@ -75,14 +73,14 @@ pub(super) fn define_flags(
                 #repr::guest_align()
             }
 
-            fn read(location: &#rt::GuestPtr<#ident>) -> Result<#ident, #rt::GuestError> {
+            fn read(location: &wiggle::GuestPtr<#ident>) -> Result<#ident, wiggle::GuestError> {
                 use std::convert::TryFrom;
                 let reprval = #repr::read(&location.cast())?;
                 let value = #ident::try_from(reprval)?;
                 Ok(value)
             }
 
-            fn write(location: &#rt::GuestPtr<'_, #ident>, val: Self) -> Result<(), #rt::GuestError> {
+            fn write(location: &wiggle::GuestPtr<'_, #ident>, val: Self) -> Result<(), wiggle::GuestError> {
                 let val: #repr = #repr::from(val);
                 #repr::write(&location.cast(), val)
             }
diff --git a/crates/wiggle/generate/src/types/handle.rs b/crates/wiggle/generate/src/types/handle.rs
index 7eb954c91..cf126484e 100644
--- a/crates/wiggle/generate/src/types/handle.rs
+++ b/crates/wiggle/generate/src/types/handle.rs
@@ -1,16 +1,11 @@
-use crate::names::Names;
+use crate::names;
 
 use proc_macro2::TokenStream;
 use quote::quote;
 use witx::Layout;
 
-pub(super) fn define_handle(
-    names: &Names,
-    name: &witx::Id,
-    h: &witx::HandleDatatype,
-) -> TokenStream {
-    let rt = names.runtime_mod();
-    let ident = names.type_(name);
+pub(super) fn define_handle(name: &witx::Id, h: &witx::HandleDatatype) -> TokenStream {
+    let ident = names::type_(name);
     let size = h.mem_size_align().size as u32;
     let align = h.mem_size_align().align as usize;
     quote! {
@@ -53,7 +48,7 @@ pub(super) fn define_handle(
             }
         }
 
-        impl<'a> #rt::GuestType<'a> for #ident {
+        impl<'a> wiggle::GuestType<'a> for #ident {
             fn guest_size() -> u32 {
                 #size
             }
@@ -62,11 +57,11 @@ pub(super) fn define_handle(
                 #align
             }
 
-            fn read(location: &#rt::GuestPtr<'a, #ident>) -> Result<#ident, #rt::GuestError> {
+            fn read(location: &wiggle::GuestPtr<'a, #ident>) -> Result<#ident, wiggle::GuestError> {
                 Ok(#ident(u32::read(&location.cast())?))
             }
 
-            fn write(location: &#rt::GuestPtr<'_, Self>, val: Self) -> Result<(), #rt::GuestError> {
+            fn write(location: &wiggle::GuestPtr<'_, Self>, val: Self) -> Result<(), wiggle::GuestError> {
                 u32::write(&location.cast(), val.0)
             }
         }
diff --git a/crates/wiggle/generate/src/types/mod.rs b/crates/wiggle/generate/src/types/mod.rs
index 255c2ce7b..2820db9fb 100644
--- a/crates/wiggle/generate/src/types/mod.rs
+++ b/crates/wiggle/generate/src/types/mod.rs
@@ -1,42 +1,49 @@
 // mod r#enum;
+mod error;
 mod flags;
 mod handle;
 mod record;
 mod variant;
 
+use crate::codegen_settings::ErrorType;
 use crate::lifetimes::LifetimeExt;
-use crate::names::Names;
+use crate::names;
 
 use proc_macro2::TokenStream;
 use quote::quote;
 
-pub fn define_datatype(names: &Names, namedtype: &witx::NamedType) -> TokenStream {
+pub fn define_datatype(namedtype: &witx::NamedType, error: Option<&ErrorType>) -> TokenStream {
     match &namedtype.tref {
-        witx::TypeRef::Name(alias_to) => define_alias(names, &namedtype.name, &alias_to),
+        witx::TypeRef::Name(alias_to) => define_alias(&namedtype.name, &alias_to),
         witx::TypeRef::Value(v) => match &**v {
             witx::Type::Record(r) => match r.bitflags_repr() {
-                Some(repr) => flags::define_flags(names, &namedtype.name, repr, &r),
-                None => record::define_struct(names, &namedtype.name, &r),
+                Some(repr) => flags::define_flags(&namedtype.name, repr, &r),
+                None => record::define_struct(&namedtype.name, &r),
             },
-            witx::Type::Variant(v) => variant::define_variant(names, &namedtype.name, &v),
-            witx::Type::Handle(h) => handle::define_handle(names, &namedtype.name, &h),
-            witx::Type::Builtin(b) => define_builtin(names, &namedtype.name, *b),
+            witx::Type::Variant(v) => match error {
+                Some(ErrorType::Generated(error)) => {
+                    let d = variant::define_variant(&namedtype.name, &v, true);
+                    let e = error::define_error(&namedtype.name, &v, error);
+                    quote!( #d #e )
+                }
+                _ => variant::define_variant(&namedtype.name, &v, false),
+            },
+            witx::Type::Handle(h) => handle::define_handle(&namedtype.name, &h),
+            witx::Type::Builtin(b) => define_builtin(&namedtype.name, *b),
             witx::Type::Pointer(p) => {
-                let rt = names.runtime_mod();
-                define_witx_pointer(names, &namedtype.name, quote!(#rt::GuestPtr), p)
+                define_witx_pointer(&namedtype.name, quote!(wiggle::GuestPtr), p)
             }
             witx::Type::ConstPointer(p) => {
-                let rt = names.runtime_mod();
-                define_witx_pointer(names, &namedtype.name, quote!(#rt::GuestPtr), p)
+                define_witx_pointer(&namedtype.name, quote!(wiggle::GuestPtr), p)
             }
-            witx::Type::List(arr) => define_witx_list(names, &namedtype.name, &arr),
+            witx::Type::List(arr) => define_witx_list(&namedtype.name, &arr),
         },
     }
 }
 
-fn define_alias(names: &Names, name: &witx::Id, to: &witx::NamedType) -> TokenStream {
-    let ident = names.type_(name);
-    let rhs = names.type_(&to.name);
+fn define_alias(name: &witx::Id, to: &witx::NamedType) -> TokenStream {
+    let ident = names::type_(name);
+    let rhs = names::type_(&to.name);
     if to.tref.needs_lifetime() {
         quote!(pub type #ident<'a> = #rhs<'a>;)
     } else {
@@ -44,29 +51,27 @@ fn define_alias(names: &Names, name: &witx::Id, to: &witx::NamedType) -> TokenSt
     }
 }
 
-fn define_builtin(names: &Names, name: &witx::Id, builtin: witx::BuiltinType) -> TokenStream {
-    let ident = names.type_(name);
-    let built = names.builtin_type(builtin);
+fn define_builtin(name: &witx::Id, builtin: witx::BuiltinType) -> TokenStream {
+    let ident = names::type_(name);
+    let built = names::builtin_type(builtin);
     quote!(pub type #ident = #built;)
 }
 
 fn define_witx_pointer(
-    names: &Names,
     name: &witx::Id,
     pointer_type: TokenStream,
     pointee: &witx::TypeRef,
 ) -> TokenStream {
-    let ident = names.type_(name);
-    let pointee_type = names.type_ref(pointee, quote!('a));
+    let ident = names::type_(name);
+    let pointee_type = names::type_ref(pointee, quote!('a));
 
     quote!(pub type #ident<'a> = #pointer_type<'a, #pointee_type>;)
 }
 
-fn define_witx_list(names: &Names, name: &witx::Id, arr_raw: &witx::TypeRef) -> TokenStream {
-    let ident = names.type_(name);
-    let rt = names.runtime_mod();
-    let pointee_type = names.type_ref(arr_raw, quote!('a));
-    quote!(pub type #ident<'a> = #rt::GuestPtr<'a, [#pointee_type]>;)
+fn define_witx_list(name: &witx::Id, arr_raw: &witx::TypeRef) -> TokenStream {
+    let ident = names::type_(name);
+    let pointee_type = names::type_ref(arr_raw, quote!('a));
+    quote!(pub type #ident<'a> = wiggle::GuestPtr<'a, [#pointee_type]>;)
 }
 
 pub fn int_repr_tokens(int_repr: witx::IntRepr) -> TokenStream {
diff --git a/crates/wiggle/generate/src/types/record.rs b/crates/wiggle/generate/src/types/record.rs
index d50124d7e..a5b48d182 100644
--- a/crates/wiggle/generate/src/types/record.rs
+++ b/crates/wiggle/generate/src/types/record.rs
@@ -1,26 +1,21 @@
 use crate::lifetimes::{anon_lifetime, LifetimeExt};
-use crate::names::Names;
+use crate::names;
 
 use proc_macro2::TokenStream;
 use quote::quote;
 use witx::Layout;
 
-pub(super) fn define_struct(
-    names: &Names,
-    name: &witx::Id,
-    s: &witx::RecordDatatype,
-) -> TokenStream {
-    let rt = names.runtime_mod();
-    let ident = names.type_(name);
+pub(super) fn define_struct(name: &witx::Id, s: &witx::RecordDatatype) -> TokenStream {
+    let ident = names::type_(name);
     let size = s.mem_size_align().size as u32;
     let align = s.mem_size_align().align as usize;
 
-    let member_names = s.members.iter().map(|m| names.struct_member(&m.name));
+    let member_names = s.members.iter().map(|m| names::struct_member(&m.name));
     let member_decls = s.members.iter().map(|m| {
-        let name = names.struct_member(&m.name);
+        let name = names::struct_member(&m.name);
         let type_ = match &m.tref {
             witx::TypeRef::Name(nt) => {
-                let tt = names.type_(&nt.name);
+                let tt = names::type_(&nt.name);
                 if m.tref.needs_lifetime() {
                     quote!(#tt<'a>)
                 } else {
@@ -28,10 +23,10 @@ pub(super) fn define_struct(
                 }
             }
             witx::TypeRef::Value(ty) => match &**ty {
-                witx::Type::Builtin(builtin) => names.builtin_type(*builtin),
+                witx::Type::Builtin(builtin) => names::builtin_type(*builtin),
                 witx::Type::Pointer(pointee) | witx::Type::ConstPointer(pointee) => {
-                    let pointee_type = names.type_ref(&pointee, quote!('a));
-                    quote!(#rt::GuestPtr<'a, #pointee_type>)
+                    let pointee_type = names::type_ref(&pointee, quote!('a));
+                    quote!(wiggle::GuestPtr<'a, #pointee_type>)
                 }
                 _ => unimplemented!("other anonymous struct members: {:?}", m.tref),
             },
@@ -40,27 +35,27 @@ pub(super) fn define_struct(
     });
 
     let member_reads = s.member_layout().into_iter().map(|ml| {
-        let name = names.struct_member(&ml.member.name);
+        let name = names::struct_member(&ml.member.name);
         let offset = ml.offset as u32;
         let location = quote!(location.cast::<u8>().add(#offset)?.cast());
         match &ml.member.tref {
             witx::TypeRef::Name(nt) => {
-                let type_ = names.type_(&nt.name);
+                let type_ = names::type_(&nt.name);
                 quote! {
-                    let #name = <#type_ as #rt::GuestType>::read(&#location)?;
+                    let #name = <#type_ as wiggle::GuestType>::read(&#location)?;
                 }
             }
             witx::TypeRef::Value(ty) => match &**ty {
                 witx::Type::Builtin(builtin) => {
-                    let type_ = names.builtin_type(*builtin);
+                    let type_ = names::builtin_type(*builtin);
                     quote! {
-                        let #name = <#type_ as #rt::GuestType>::read(&#location)?;
+                        let #name = <#type_ as wiggle::GuestType>::read(&#location)?;
                     }
                 }
                 witx::Type::Pointer(pointee) | witx::Type::ConstPointer(pointee) => {
-                    let pointee_type = names.type_ref(&pointee, anon_lifetime());
+                    let pointee_type = names::type_ref(&pointee, anon_lifetime());
                     quote! {
-                        let #name = <#rt::GuestPtr::<#pointee_type> as #rt::GuestType>::read(&#location)?;
+                        let #name = <wiggle::GuestPtr::<#pointee_type> as wiggle::GuestType>::read(&#location)?;
                     }
                 }
                 _ => unimplemented!("other anonymous struct members: {:?}", ty),
@@ -69,10 +64,10 @@ pub(super) fn define_struct(
     });
 
     let member_writes = s.member_layout().into_iter().map(|ml| {
-        let name = names.struct_member(&ml.member.name);
+        let name = names::struct_member(&ml.member.name);
         let offset = ml.offset as u32;
         quote! {
-            #rt::GuestType::write(
+            wiggle::GuestType::write(
                 &location.cast::<u8>().add(#offset)?.cast(),
                 val.#name,
             )?;
@@ -91,7 +86,7 @@ pub(super) fn define_struct(
             #(#member_decls),*
         }
 
-        impl<'a> #rt::GuestType<'a> for #ident #struct_lifetime {
+        impl<'a> wiggle::GuestType<'a> for #ident #struct_lifetime {
             fn guest_size() -> u32 {
                 #size
             }
@@ -100,12 +95,12 @@ pub(super) fn define_struct(
                 #align
             }
 
-            fn read(location: &#rt::GuestPtr<'a, Self>) -> Result<Self, #rt::GuestError> {
+            fn read(location: &wiggle::GuestPtr<'a, Self>) -> Result<Self, wiggle::GuestError> {
                 #(#member_reads)*
                 Ok(#ident { #(#member_names),* })
             }
 
-            fn write(location: &#rt::GuestPtr<'_, Self>, val: Self) -> Result<(), #rt::GuestError> {
+            fn write(location: &wiggle::GuestPtr<'_, Self>, val: Self) -> Result<(), wiggle::GuestError> {
                 #(#member_writes)*
                 Ok(())
             }
diff --git a/crates/wiggle/generate/src/types/variant.rs b/crates/wiggle/generate/src/types/variant.rs
index 5624b43eb..c16e3bc72 100644
--- a/crates/wiggle/generate/src/types/variant.rs
+++ b/crates/wiggle/generate/src/types/variant.rs
@@ -1,13 +1,16 @@
 use crate::lifetimes::LifetimeExt;
-use crate::names::Names;
+use crate::names;
 
 use proc_macro2::{Literal, TokenStream};
 use quote::quote;
 use witx::Layout;
 
-pub(super) fn define_variant(names: &Names, name: &witx::Id, v: &witx::Variant) -> TokenStream {
-    let rt = names.runtime_mod();
-    let ident = names.type_(name);
+pub(super) fn define_variant(
+    name: &witx::Id,
+    v: &witx::Variant,
+    derive_std_error: bool,
+) -> TokenStream {
+    let ident = names::type_(name);
     let size = v.mem_size_align().size as u32;
     let align = v.mem_size_align().align as usize;
     let contents_offset = v.payload_offset() as u32;
@@ -16,9 +19,9 @@ pub(super) fn define_variant(names: &Names, name: &witx::Id, v: &witx::Variant)
     let tag_ty = super::int_repr_tokens(v.tag_repr);
 
     let variants = v.cases.iter().map(|c| {
-        let var_name = names.enum_variant(&c.name);
+        let var_name = names::enum_variant(&c.name);
         if let Some(tref) = &c.tref {
-            let var_type = names.type_ref(&tref, lifetime.clone());
+            let var_type = names::type_ref(&tref, lifetime.clone());
             quote!(#var_name(#var_type))
         } else {
             quote!(#var_name)
@@ -27,13 +30,13 @@ pub(super) fn define_variant(names: &Names, name: &witx::Id, v: &witx::Variant)
 
     let read_variant = v.cases.iter().enumerate().map(|(i, c)| {
         let i = Literal::usize_unsuffixed(i);
-        let variantname = names.enum_variant(&c.name);
+        let variantname = names::enum_variant(&c.name);
         if let Some(tref) = &c.tref {
-            let varianttype = names.type_ref(tref, lifetime.clone());
+            let varianttype = names::type_ref(tref, lifetime.clone());
             quote! {
                 #i => {
                     let variant_ptr = location.cast::<u8>().add(#contents_offset)?;
-                    let variant_val = <#varianttype as #rt::GuestType>::read(&variant_ptr.cast())?;
+                    let variant_val = <#varianttype as wiggle::GuestType>::read(&variant_ptr.cast())?;
                     Ok(#ident::#variantname(variant_val))
                 }
             }
@@ -43,17 +46,17 @@ pub(super) fn define_variant(names: &Names, name: &witx::Id, v: &witx::Variant)
     });
 
     let write_variant = v.cases.iter().enumerate().map(|(i, c)| {
-        let variantname = names.enum_variant(&c.name);
+        let variantname = names::enum_variant(&c.name);
         let write_tag = quote! {
             location.cast().write(#i as #tag_ty)?;
         };
         if let Some(tref) = &c.tref {
-            let varianttype = names.type_ref(tref, lifetime.clone());
+            let varianttype = names::type_ref(tref, lifetime.clone());
             quote! {
                 #ident::#variantname(contents) => {
                     #write_tag
                     let variant_ptr = location.cast::<u8>().add(#contents_offset)?;
-                    <#varianttype as #rt::GuestType>::write(&variant_ptr.cast(), contents)?;
+                    <#varianttype as wiggle::GuestType>::write(&variant_ptr.cast(), contents)?;
                 }
             }
         } else {
@@ -68,26 +71,26 @@ pub(super) fn define_variant(names: &Names, name: &witx::Id, v: &witx::Variant)
     let mut extra_derive = quote!();
     let enum_try_from = if v.cases.iter().all(|c| c.tref.is_none()) {
         let tryfrom_repr_cases = v.cases.iter().enumerate().map(|(i, c)| {
-            let variant_name = names.enum_variant(&c.name);
+            let variant_name = names::enum_variant(&c.name);
             let n = Literal::usize_unsuffixed(i);
             quote!(#n => Ok(#ident::#variant_name))
         });
-        let abi_ty = names.wasm_type(v.tag_repr.into());
+        let abi_ty = names::wasm_type(v.tag_repr.into());
         extra_derive = quote!(, Copy);
         quote! {
             impl TryFrom<#tag_ty> for #ident {
-                type Error = #rt::GuestError;
-                fn try_from(value: #tag_ty) -> Result<#ident, #rt::GuestError> {
+                type Error = wiggle::GuestError;
+                fn try_from(value: #tag_ty) -> Result<#ident, wiggle::GuestError> {
                     match value {
                         #(#tryfrom_repr_cases),*,
-                        _ => Err( #rt::GuestError::InvalidEnumValue(stringify!(#ident))),
+                        _ => Err(wiggle::GuestError::InvalidEnumValue(stringify!(#ident))),
                     }
                 }
             }
 
             impl TryFrom<#abi_ty> for #ident {
-                type Error = #rt::GuestError;
-                fn try_from(value: #abi_ty) -> Result<#ident, #rt::GuestError> {
+                type Error = wiggle::GuestError;
+                fn try_from(value: #abi_ty) -> Result<#ident, wiggle::GuestError> {
                     #ident::try_from(#tag_ty::try_from(value)?)
                 }
             }
@@ -98,7 +101,7 @@ pub(super) fn define_variant(names: &Names, name: &witx::Id, v: &witx::Variant)
 
     let enum_from = if v.cases.iter().all(|c| c.tref.is_none()) {
         let from_repr_cases = v.cases.iter().enumerate().map(|(i, c)| {
-            let variant_name = names.enum_variant(&c.name);
+            let variant_name = names::enum_variant(&c.name);
             let n = Literal::usize_unsuffixed(i);
             quote!(#ident::#variant_name => #n)
         });
@@ -121,16 +124,30 @@ pub(super) fn define_variant(names: &Names, name: &witx::Id, v: &witx::Variant)
         (quote!(), quote!(, PartialEq #extra_derive))
     };
 
+    let error_impls = if derive_std_error {
+        quote! {
+            impl std::fmt::Display for #ident {
+                fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+                    write!(f, "{:?}", self)
+                }
+            }
+            impl std::error::Error for #ident {}
+        }
+    } else {
+        quote!()
+    };
+
     quote! {
         #[derive(Clone, Debug #extra_derive)]
         pub enum #ident #enum_lifetime {
             #(#variants),*
         }
+        #error_impls
 
         #enum_try_from
         #enum_from
 
-        impl<'a> #rt::GuestType<'a> for #ident #enum_lifetime {
+        impl<'a> wiggle::GuestType<'a> for #ident #enum_lifetime {
             fn guest_size() -> u32 {
                 #size
             }
@@ -139,19 +156,19 @@ pub(super) fn define_variant(names: &Names, name: &witx::Id, v: &witx::Variant)
                 #align
             }
 
-            fn read(location: &#rt::GuestPtr<'a, Self>)
-                -> Result<Self, #rt::GuestError>
+            fn read(location: &wiggle::GuestPtr<'a, Self>)
+                -> Result<Self, wiggle::GuestError>
             {
                 let tag = location.cast::<#tag_ty>().read()?;
                 match tag {
                     #(#read_variant)*
-                    _ => Err(#rt::GuestError::InvalidEnumValue(stringify!(#ident))),
+                    _ => Err(wiggle::GuestError::InvalidEnumValue(stringify!(#ident))),
                 }
 
             }
 
-            fn write(location: &#rt::GuestPtr<'_, Self>, val: Self)
-                -> Result<(), #rt::GuestError>
+            fn write(location: &wiggle::GuestPtr<'_, Self>, val: Self)
+                -> Result<(), wiggle::GuestError>
             {
                 match val {
                     #(#write_variant)*
diff --git a/crates/wiggle/generate/src/wasmtime.rs b/crates/wiggle/generate/src/wasmtime.rs
index 98c9f342c..fcab9da62 100644
--- a/crates/wiggle/generate/src/wasmtime.rs
+++ b/crates/wiggle/generate/src/wasmtime.rs
@@ -1,17 +1,17 @@
 use crate::config::Asyncness;
 use crate::funcs::func_bounds;
-use crate::{CodegenSettings, Names};
+use crate::names;
+use crate::CodegenSettings;
 use proc_macro2::{Ident, Span, TokenStream};
 use quote::{format_ident, quote};
 use std::collections::HashSet;
 
 pub fn link_module(
     module: &witx::Module,
-    names: &Names,
     target_path: Option<&syn::Path>,
     settings: &CodegenSettings,
 ) -> TokenStream {
-    let module_ident = names.module(&module.name);
+    let module_ident = names::module(&module.name);
 
     let send_bound = if settings.async_.contains_async(module) {
         quote! { + Send, T: Send }
@@ -23,8 +23,8 @@ pub fn link_module(
     let mut bounds = HashSet::new();
     for f in module.funcs() {
         let asyncness = settings.async_.get(module.name.as_str(), f.name.as_str());
-        bodies.push(generate_func(&module, &f, names, target_path, asyncness));
-        let bound = func_bounds(names, module, &f, settings);
+        bodies.push(generate_func(&module, &f, target_path, asyncness));
+        let bound = func_bounds(module, &f, settings);
         for b in bound {
             bounds.insert(b);
         }
@@ -46,14 +46,12 @@ pub fn link_module(
         format_ident!("add_{}_to_linker", module_ident)
     };
 
-    let rt = names.runtime_mod();
-
     quote! {
         /// Adds all instance items to the specified `Linker`.
         pub fn #func_name<T, U>(
-            linker: &mut #rt::wasmtime_crate::Linker<T>,
+            linker: &mut wiggle::wasmtime_crate::Linker<T>,
             get_cx: impl Fn(&mut T) -> &mut U + Send + Sync + Copy + 'static,
-        ) -> #rt::anyhow::Result<()>
+        ) -> wiggle::anyhow::Result<()>
             where
                 U: #ctx_bound #send_bound
         {
@@ -66,17 +64,14 @@ pub fn link_module(
 fn generate_func(
     module: &witx::Module,
     func: &witx::InterfaceFunc,
-    names: &Names,
     target_path: Option<&syn::Path>,
     asyncness: Asyncness,
 ) -> TokenStream {
-    let rt = names.runtime_mod();
-
     let module_str = module.name.as_str();
-    let module_ident = names.module(&module.name);
+    let module_ident = names::module(&module.name);
 
     let field_str = func.name.as_str();
-    let field_ident = names.func(&func.name);
+    let field_ident = names::func(&func.name);
 
     let (params, results) = func.wasm_signature();
 
@@ -88,14 +83,14 @@ fn generate_func(
         .enumerate()
         .map(|(i, ty)| {
             let name = &arg_names[i];
-            let wasm = names.wasm_type(*ty);
+            let wasm = names::wasm_type(*ty);
             quote! { #name: #wasm }
         })
         .collect::<Vec<_>>();
 
     let ret_ty = match results.len() {
         0 => quote!(()),
-        1 => names.wasm_type(results[0]),
+        1 => names::wasm_type(results[0]),
         _ => unimplemented!(),
     };
 
@@ -114,16 +109,16 @@ fn generate_func(
     let body = quote! {
         let export = caller.get_export("memory");
         let (mem, ctx) = match &export {
-            Some(#rt::wasmtime_crate::Extern::Memory(m)) => {
+            Some(wiggle::wasmtime_crate::Extern::Memory(m)) => {
                 let (mem, ctx) = m.data_and_store_mut(&mut caller);
                 let ctx = get_cx(ctx);
-                (#rt::wasmtime::WasmtimeGuestMemory::new(mem), ctx)
+                (wiggle::wasmtime::WasmtimeGuestMemory::new(mem), ctx)
             }
-            Some(#rt::wasmtime_crate::Extern::SharedMemory(m)) => {
+            Some(wiggle::wasmtime_crate::Extern::SharedMemory(m)) => {
                 let ctx = get_cx(caller.data_mut());
-                (#rt::wasmtime::WasmtimeGuestMemory::shared(m.data()), ctx)
+                (wiggle::wasmtime::WasmtimeGuestMemory::shared(m.data()), ctx)
             }
-            _ => #rt::anyhow::bail!("missing required memory export"),
+            _ => wiggle::anyhow::bail!("missing required memory export"),
         };
         Ok(<#ret_ty>::from(#abi_func(ctx, &mem #(, #arg_names)*) #await_ ?))
     };
@@ -135,7 +130,7 @@ fn generate_func(
                 linker.#wrapper(
                     #module_str,
                     #field_str,
-                    move |mut caller: #rt::wasmtime_crate::Caller<'_, T> #(, #arg_decls)*| {
+                    move |mut caller: wiggle::wasmtime_crate::Caller<'_, T> #(, #arg_decls)*| {
                         Box::new(async move { #body })
                     },
                 )?;
@@ -147,9 +142,9 @@ fn generate_func(
                 linker.func_wrap(
                     #module_str,
                     #field_str,
-                    move |mut caller: #rt::wasmtime_crate::Caller<'_, T> #(, #arg_decls)*| -> #rt::anyhow::Result<#ret_ty> {
+                    move |mut caller: wiggle::wasmtime_crate::Caller<'_, T> #(, #arg_decls)*| -> wiggle::anyhow::Result<#ret_ty> {
                         let result = async { #body };
-                        #rt::run_in_dummy_executor(result)?
+                        wiggle::run_in_dummy_executor(result)?
                     },
                 )?;
             }
@@ -160,7 +155,7 @@ fn generate_func(
                 linker.func_wrap(
                     #module_str,
                     #field_str,
-                    move |mut caller: #rt::wasmtime_crate::Caller<'_, T> #(, #arg_decls)*| -> #rt::anyhow::Result<#ret_ty> {
+                    move |mut caller: wiggle::wasmtime_crate::Caller<'_, T> #(, #arg_decls)*| -> wiggle::anyhow::Result<#ret_ty> {
                         #body
                     },
                 )?;
diff --git a/crates/wiggle/macro/src/lib.rs b/crates/wiggle/macro/src/lib.rs
index 11bbbfc01..9ea2cf104 100644
--- a/crates/wiggle/macro/src/lib.rs
+++ b/crates/wiggle/macro/src/lib.rs
@@ -39,6 +39,10 @@ use syn::parse_macro_input;
 ///   `{ errno => YourErrnoType }`. This allows you to use the `UserErrorConversion`
 ///   trait to map these rich errors into the flat witx type, or to terminate
 ///   WebAssembly execution by trapping.
+///     * Instead of requiring the user to define an error type, wiggle can
+///       generate an error type for the user which has conversions to/from
+///       the base type, and permits trapping, using the syntax
+///       `errno => trappable AnErrorType`.
 /// * Optional: `async` takes a set of witx modules and functions which are
 ///   made Rust `async` functions in the module trait.
 ///
@@ -146,7 +150,6 @@ pub fn from_witx(args: TokenStream) -> TokenStream {
     let config = parse_macro_input!(args as wiggle_generate::Config);
 
     let doc = config.load_document();
-    let names = wiggle_generate::Names::new(quote!(wiggle));
 
     let settings = wiggle_generate::CodegenSettings::new(
         &config.errors,
@@ -157,9 +160,9 @@ pub fn from_witx(args: TokenStream) -> TokenStream {
     )
     .expect("validating codegen settings");
 
-    let code = wiggle_generate::generate(&doc, &names, &settings);
+    let code = wiggle_generate::generate(&doc, &settings);
     let metadata = if cfg!(feature = "wiggle_metadata") {
-        wiggle_generate::generate_metadata(&doc, &names)
+        wiggle_generate::generate_metadata(&doc)
     } else {
         quote!()
     };
@@ -188,7 +191,6 @@ pub fn async_trait(attr: TokenStream, item: TokenStream) -> TokenStream {
 pub fn wasmtime_integration(args: TokenStream) -> TokenStream {
     let config = parse_macro_input!(args as wiggle_generate::WasmtimeConfig);
     let doc = config.c.load_document();
-    let names = wiggle_generate::Names::new(quote!(wiggle));
 
     let settings = wiggle_generate::CodegenSettings::new(
         &config.c.errors,
@@ -200,7 +202,7 @@ pub fn wasmtime_integration(args: TokenStream) -> TokenStream {
     .expect("validating codegen settings");
 
     let modules = doc.modules().map(|module| {
-        wiggle_generate::wasmtime::link_module(&module, &names, Some(&config.target), &settings)
+        wiggle_generate::wasmtime::link_module(&module, Some(&config.target), &settings)
     });
     quote!( #(#modules)* ).into()
 }
diff --git a/docs/lang-rust.md b/docs/lang-rust.md
index 5a4a3747e..cc5d57248 100644
--- a/docs/lang-rust.md
+++ b/docs/lang-rust.md
@@ -86,7 +86,7 @@ fn main() -> Result<(), Box<dyn Error>> {
     // There's a few ways we can call the `answer` `Func` value. The easiest
     // is to statically assert its signature with `typed` (in this case
     // asserting it takes no arguments and returns one i32) and then call it.
-    let answer = answer.typed::<(), i32, _>(&store)?;
+    let answer = answer.typed::<(), i32>(&store)?;
 
     // And finally we can call our function! Note that the error propagation
     // with `?` is done to handle the case where the wasm function traps.
@@ -184,7 +184,7 @@ fn main() -> Result<(), Box<dyn Error>> {
     let instance = linker.instantiate(&mut store, &module)?;
 
     // Like before, we can get the run function and execute it.
-    let run = instance.get_typed_func::<(), (), _>(&mut store, "run")?;
+    let run = instance.get_typed_func::<(), ()>(&mut store, "run")?;
     run.call(&mut store, ())?;
 
     // We can also inspect what integers were logged:
diff --git a/docs/wasm-wat.md b/docs/wasm-wat.md
index c08fc49e7..634d35730 100644
--- a/docs/wasm-wat.md
+++ b/docs/wasm-wat.md
@@ -47,7 +47,7 @@ let wat = r#"
 "#;
 let module = Module::new(store.engine(), wat)?;
 let instance = Instance::new(&mut store, &module, &[])?;
-let add = instance.get_typed_func::<(i32, i32), i32, _>(&mut store, "add")?;
+let add = instance.get_typed_func::<(i32, i32), i32>(&mut store, "add")?;
 println!("1 + 2 = {}", add.call(&mut store, (1, 2))?);
 # Ok(())
 # }
diff --git a/examples/epochs.rs b/examples/epochs.rs
index fd19fd28d..47d72d7b3 100644
--- a/examples/epochs.rs
+++ b/examples/epochs.rs
@@ -36,7 +36,7 @@ fn main() -> Result<(), Error> {
 
     // Invoke `fibonacci` with a large argument such that a normal
     // invocation would take many seconds to complete.
-    let fibonacci = instance.get_typed_func::<i32, i32, _>(&mut store, "fibonacci")?;
+    let fibonacci = instance.get_typed_func::<i32, i32>(&mut store, "fibonacci")?;
     match fibonacci.call(&mut store, 100) {
         Ok(_) => panic!("Somehow we computed recursive fib(100) in less than a second!"),
         Err(_) => {
diff --git a/examples/externref.rs b/examples/externref.rs
index 794e5fbb7..67c91d8af 100644
--- a/examples/externref.rs
+++ b/examples/externref.rs
@@ -44,7 +44,7 @@ fn main() -> Result<()> {
 
     println!("Calling `externref` func...");
     let func =
-        instance.get_typed_func::<Option<ExternRef>, Option<ExternRef>, _>(&mut store, "func")?;
+        instance.get_typed_func::<Option<ExternRef>, Option<ExternRef>>(&mut store, "func")?;
     let ret = func.call(&mut store, Some(externref.clone()))?;
     assert!(ret.is_some());
     assert!(ret.unwrap().ptr_eq(&externref));
diff --git a/examples/fib-debug/main.rs b/examples/fib-debug/main.rs
index 66216081a..b9e51f229 100644
--- a/examples/fib-debug/main.rs
+++ b/examples/fib-debug/main.rs
@@ -21,7 +21,7 @@ fn main() -> Result<()> {
     let instance = Instance::new(&mut store, &module, &[])?;
 
     // Invoke `fib` export
-    let fib = instance.get_typed_func::<i32, i32, _>(&mut store, "fib")?;
+    let fib = instance.get_typed_func::<i32, i32>(&mut store, "fib")?;
     println!("fib(6) = {}", fib.call(&mut store, 6)?);
     Ok(())
 }
diff --git a/examples/fuel.rs b/examples/fuel.rs
index acca9f2d9..e6f4c74ec 100644
--- a/examples/fuel.rs
+++ b/examples/fuel.rs
@@ -15,7 +15,7 @@ fn main() -> Result<()> {
     let instance = Instance::new(&mut store, &module, &[])?;
 
     // Invoke `fibonacci` export with higher and higher numbers until we exhaust our fuel.
-    let fibonacci = instance.get_typed_func::<i32, i32, _>(&mut store, "fibonacci")?;
+    let fibonacci = instance.get_typed_func::<i32, i32>(&mut store, "fibonacci")?;
     for n in 1.. {
         let fuel_before = store.fuel_consumed().unwrap();
         let output = match fibonacci.call(&mut store, n) {
diff --git a/examples/gcd.rs b/examples/gcd.rs
index a748c6d82..176ee656e 100644
--- a/examples/gcd.rs
+++ b/examples/gcd.rs
@@ -15,7 +15,7 @@ fn main() -> Result<()> {
     let instance = Instance::new(&mut store, &module, &[])?;
 
     // Invoke `gcd` export
-    let gcd = instance.get_typed_func::<(i32, i32), i32, _>(&mut store, "gcd")?;
+    let gcd = instance.get_typed_func::<(i32, i32), i32>(&mut store, "gcd")?;
 
     println!("gcd(6, 27) = {}", gcd.call(&mut store, (6, 27))?);
     Ok(())
diff --git a/examples/hello.rs b/examples/hello.rs
index 62ae9fa35..67df450ec 100644
--- a/examples/hello.rs
+++ b/examples/hello.rs
@@ -53,7 +53,7 @@ fn main() -> Result<()> {
 
     // Next we poke around a bit to extract the `run` function from the module.
     println!("Extracting export...");
-    let run = instance.get_typed_func::<(), (), _>(&mut store, "run")?;
+    let run = instance.get_typed_func::<(), ()>(&mut store, "run")?;
 
     // And last but not least we can call it!
     println!("Calling export...");
diff --git a/examples/interrupt.rs b/examples/interrupt.rs
index 749a63304..87d6d4dd0 100644
--- a/examples/interrupt.rs
+++ b/examples/interrupt.rs
@@ -16,7 +16,7 @@ fn main() -> Result<()> {
     // Compile and instantiate a small example with an infinite loop.
     let module = Module::from_file(&engine, "examples/interrupt.wat")?;
     let instance = Instance::new(&mut store, &module, &[])?;
-    let run = instance.get_typed_func::<(), (), _>(&mut store, "run")?;
+    let run = instance.get_typed_func::<(), ()>(&mut store, "run")?;
 
     // Spin up a thread to send us an interrupt in a second
     std::thread::spawn(move || {
diff --git a/examples/linking.rs b/examples/linking.rs
index fbcd11dfd..a2d756057 100644
--- a/examples/linking.rs
+++ b/examples/linking.rs
@@ -32,7 +32,7 @@ fn main() -> Result<()> {
 
     // And with that we can perform the final link and the execute the module.
     let linking1 = linker.instantiate(&mut store, &linking1)?;
-    let run = linking1.get_typed_func::<(), (), _>(&mut store, "run")?;
+    let run = linking1.get_typed_func::<(), ()>(&mut store, "run")?;
     run.call(&mut store, ())?;
     Ok(())
 }
diff --git a/examples/memory.rs b/examples/memory.rs
index ab971b2d0..93c712cf1 100644
--- a/examples/memory.rs
+++ b/examples/memory.rs
@@ -20,9 +20,9 @@ fn main() -> Result<()> {
     let memory = instance
         .get_memory(&mut store, "memory")
         .ok_or(anyhow::format_err!("failed to find `memory` export"))?;
-    let size = instance.get_typed_func::<(), i32, _>(&mut store, "size")?;
-    let load_fn = instance.get_typed_func::<i32, i32, _>(&mut store, "load")?;
-    let store_fn = instance.get_typed_func::<(i32, i32), (), _>(&mut store, "store")?;
+    let size = instance.get_typed_func::<(), i32>(&mut store, "size")?;
+    let load_fn = instance.get_typed_func::<i32, i32>(&mut store, "load")?;
+    let store_fn = instance.get_typed_func::<(i32, i32), ()>(&mut store, "store")?;
 
     println!("Checking memory...");
     assert_eq!(memory.size(&store), 2);
diff --git a/examples/multi.rs b/examples/multi.rs
index b243d83cd..8f495847b 100644
--- a/examples/multi.rs
+++ b/examples/multi.rs
@@ -33,7 +33,7 @@ fn main() -> Result<()> {
 
     // Extract exports.
     println!("Extracting export...");
-    let g = instance.get_typed_func::<(i32, i64), (i64, i32), _>(&mut store, "g")?;
+    let g = instance.get_typed_func::<(i32, i64), (i64, i32)>(&mut store, "g")?;
 
     // Call `$g`.
     println!("Calling export \"g\"...");
@@ -51,7 +51,6 @@ fn main() -> Result<()> {
         .get_typed_func::<
         (i64, i64, i64, i64, i64, i64, i64, i64, i64, i64),
         (i64, i64, i64, i64, i64, i64, i64, i64, i64, i64),
-        _,
         >
         (&mut store, "round_trip_many")?;
     let results = round_trip_many.call(&mut store, (0, 1, 2, 3, 4, 5, 6, 7, 8, 9))?;
diff --git a/examples/multimemory.rs b/examples/multimemory.rs
index 9a1dd8e16..023817f6a 100644
--- a/examples/multimemory.rs
+++ b/examples/multimemory.rs
@@ -25,16 +25,16 @@ fn main() -> Result<()> {
     let memory0 = instance
         .get_memory(&mut store, "memory0")
         .ok_or(anyhow::format_err!("failed to find `memory0` export"))?;
-    let size0 = instance.get_typed_func::<(), i32, _>(&mut store, "size0")?;
-    let load0 = instance.get_typed_func::<i32, i32, _>(&mut store, "load0")?;
-    let store0 = instance.get_typed_func::<(i32, i32), (), _>(&mut store, "store0")?;
+    let size0 = instance.get_typed_func::<(), i32>(&mut store, "size0")?;
+    let load0 = instance.get_typed_func::<i32, i32>(&mut store, "load0")?;
+    let store0 = instance.get_typed_func::<(i32, i32), ()>(&mut store, "store0")?;
 
     let memory1 = instance
         .get_memory(&mut store, "memory1")
         .ok_or(anyhow::format_err!("failed to find `memory1` export"))?;
-    let size1 = instance.get_typed_func::<(), i32, _>(&mut store, "size1")?;
-    let load1 = instance.get_typed_func::<i32, i32, _>(&mut store, "load1")?;
-    let store1 = instance.get_typed_func::<(i32, i32), (), _>(&mut store, "store1")?;
+    let size1 = instance.get_typed_func::<(), i32>(&mut store, "size1")?;
+    let load1 = instance.get_typed_func::<i32, i32>(&mut store, "load1")?;
+    let store1 = instance.get_typed_func::<(i32, i32), ()>(&mut store, "store1")?;
 
     println!("Checking memory...");
     assert_eq!(memory0.size(&store), 2);
diff --git a/examples/serialize.rs b/examples/serialize.rs
index 22a281698..0d38fd5f5 100644
--- a/examples/serialize.rs
+++ b/examples/serialize.rs
@@ -53,7 +53,7 @@ fn deserialize(buffer: &[u8]) -> Result<()> {
 
     // Next we poke around a bit to extract the `run` function from the module.
     println!("Extracting export...");
-    let run = instance.get_typed_func::<(), (), _>(&mut store, "run")?;
+    let run = instance.get_typed_func::<(), ()>(&mut store, "run")?;
 
     // And last but not least we can call it!
     println!("Calling export...");
diff --git a/examples/threads.rs b/examples/threads.rs
index 9586022a5..3c3564833 100644
--- a/examples/threads.rs
+++ b/examples/threads.rs
@@ -52,7 +52,7 @@ fn run(engine: &Engine, module: &Module, linker: &Linker<()>) -> Result<()> {
     println!("Instantiating module...");
     let mut store = Store::new(&engine, ());
     let instance = linker.instantiate(&mut store, module)?;
-    let run = instance.get_typed_func::<(), (), _>(&mut store, "run")?;
+    let run = instance.get_typed_func::<(), ()>(&mut store, "run")?;
 
     println!("Executing...");
     for _ in 0..N_REPS {
diff --git a/examples/tokio/main.rs b/examples/tokio/main.rs
index f26e7beae..e90f6de97 100644
--- a/examples/tokio/main.rs
+++ b/examples/tokio/main.rs
@@ -106,7 +106,7 @@ async fn run_wasm(inputs: Inputs) -> Result<(), Error> {
         .instantiate_async(&mut store, &inputs.env.module)
         .await?;
     instance
-        .get_typed_func::<(), (), _>(&mut store, "_start")?
+        .get_typed_func::<(), ()>(&mut store, "_start")?
         .call_async(&mut store, ())
         .await?;
 
diff --git a/examples/wasi/main.rs b/examples/wasi/main.rs
index 8ffd98d78..30d8e1f2a 100644
--- a/examples/wasi/main.rs
+++ b/examples/wasi/main.rs
@@ -30,7 +30,7 @@ fn main() -> Result<()> {
     linker.module(&mut store, "", &module)?;
     linker
         .get_default(&mut store, "")?
-        .typed::<(), (), _>(&store)?
+        .typed::<(), ()>(&store)?
         .call(&mut store, ())?;
 
     Ok(())
diff --git a/src/commands/compile.rs b/src/commands/compile.rs
index 0988e5749..e7562447a 100644
--- a/src/commands/compile.rs
+++ b/src/commands/compile.rs
@@ -133,7 +133,7 @@ mod test {
         let module = unsafe { Module::deserialize(&engine, contents)? };
         let mut store = Store::new(&engine, ());
         let instance = Instance::new(&mut store, &module, &[])?;
-        let f = instance.get_typed_func::<i32, i32, _>(&mut store, "f")?;
+        let f = instance.get_typed_func::<i32, i32>(&mut store, "f")?;
         assert_eq!(f.call(&mut store, 1234).unwrap(), 1234);
 
         Ok(())


Test patch:
diff --git a/crates/test-programs/tests/wasm_tests/runtime/cap_std_sync.rs b/crates/test-programs/tests/wasm_tests/runtime/cap_std_sync.rs
index 80569ee7a..939b0bdb9 100644
--- a/crates/test-programs/tests/wasm_tests/runtime/cap_std_sync.rs
+++ b/crates/test-programs/tests/wasm_tests/runtime/cap_std_sync.rs
@@ -59,7 +59,7 @@ fn run(
 
         let mut store = Store::new(&engine, builder.build());
         let instance = linker.instantiate(&mut store, &module)?;
-        let start = instance.get_typed_func::<(), (), _>(&mut store, "_start")?;
+        let start = instance.get_typed_func::<(), ()>(&mut store, "_start")?;
         start.call(&mut store, ()).map_err(anyhow::Error::from)
     };
 
diff --git a/crates/test-programs/tests/wasm_tests/runtime/tokio.rs b/crates/test-programs/tests/wasm_tests/runtime/tokio.rs
index 3eb23e8a3..565dc088d 100644
--- a/crates/test-programs/tests/wasm_tests/runtime/tokio.rs
+++ b/crates/test-programs/tests/wasm_tests/runtime/tokio.rs
@@ -67,7 +67,7 @@ fn run(
             let mut store = Store::new(&engine, builder.build());
 
             let instance = linker.instantiate_async(&mut store, &module).await?;
-            let start = instance.get_typed_func::<(), (), _>(&mut store, "_start")?;
+            let start = instance.get_typed_func::<(), ()>(&mut store, "_start")?;
             start
                 .call_async(&mut store, ())
                 .await
diff --git a/crates/wiggle/tests/errors.rs b/crates/wiggle/tests/errors.rs
index 737b7ea5a..594937843 100644
--- a/crates/wiggle/tests/errors.rs
+++ b/crates/wiggle/tests/errors.rs
@@ -24,34 +24,32 @@ mod convert_just_errno {
      (param $strike u32)
      (result $err (expected (error $errno)))))
     ",
-        errors: { errno => RichError },
+        errors: { errno => trappable ErrnoT },
     });
 
     impl_errno!(types::Errno);
 
-    /// When the `errors` mapping in witx is non-empty, we need to impl the
-    /// types::UserErrorConversion trait that wiggle generates from that mapping.
-    impl<'a> types::UserErrorConversion for WasiCtx<'a> {
-        fn errno_from_rich_error(&mut self, e: RichError) -> Result<types::Errno> {
-            // WasiCtx can collect a Vec<String> log so we can test this. We're
-            // logging the Display impl that `thiserror::Error` provides us.
-            self.log.borrow_mut().push(e.to_string());
-            // Then do the trivial mapping down to the flat enum.
-            match e {
-                RichError::InvalidArg { .. } => Ok(types::Errno::InvalidArg),
-                RichError::PicketLine { .. } => Ok(types::Errno::PicketLine),
+    impl From<RichError> for types::ErrnoT {
+        fn from(rich: RichError) -> types::ErrnoT {
+            match rich {
+                RichError::InvalidArg(s) => {
+                    types::ErrnoT::from(types::Errno::InvalidArg).context(s)
+                }
+                RichError::PicketLine(s) => {
+                    types::ErrnoT::from(types::Errno::PicketLine).context(s)
+                }
             }
         }
     }
 
     impl<'a> one_error_conversion::OneErrorConversion for WasiCtx<'a> {
-        fn foo(&mut self, strike: u32) -> Result<(), RichError> {
+        fn foo(&mut self, strike: u32) -> Result<(), types::ErrnoT> {
             // We use the argument to this function to exercise all of the
             // possible error cases we could hit here
             match strike {
                 0 => Ok(()),
-                1 => Err(RichError::PicketLine(format!("I'm not a scab"))),
-                _ => Err(RichError::InvalidArg(format!("out-of-bounds: {}", strike))),
+                1 => Err(RichError::PicketLine(format!("I'm not a scab")))?,
+                _ => Err(RichError::InvalidArg(format!("out-of-bounds: {}", strike)))?,
             }
         }
     }
@@ -78,11 +76,6 @@ mod convert_just_errno {
             types::Errno::PicketLine as i32,
             "Expected return value for strike=1"
         );
-        assert_eq!(
-            ctx.log.borrow_mut().pop().expect("one log entry"),
-            "Won't cross picket line: I'm not a scab",
-            "Expected log entry for strike=1",
-        );
 
         // Second error case:
         let r2 = one_error_conversion::foo(&mut ctx, &host_memory, 2).unwrap();
@@ -91,11 +84,6 @@ mod convert_just_errno {
             types::Errno::InvalidArg as i32,
             "Expected return value for strike=2"
         );
-        assert_eq!(
-            ctx.log.borrow_mut().pop().expect("one log entry"),
-            "Invalid argument: out-of-bounds: 2",
-            "Expected log entry for strike=2",
-        );
     }
 }
 
diff --git a/tests/all/async_functions.rs b/tests/all/async_functions.rs
index d2b0d03cb..7d8a389b3 100644
--- a/tests/all/async_functions.rs
+++ b/tests/all/async_functions.rs
@@ -14,7 +14,7 @@ async fn run_smoke_test(store: &mut Store<()>, func: Func) {
 }
 
 async fn run_smoke_typed_test(store: &mut Store<()>, func: Func) {
-    let func = func.typed::<(), (), _>(&store).unwrap();
+    let func = func.typed::<(), ()>(&store).unwrap();
     func.call_async(&mut *store, ()).await.unwrap();
     func.call_async(&mut *store, ()).await.unwrap();
 }
@@ -545,8 +545,8 @@ async fn recursive_async() -> Result<()> {
         )",
     )?;
     let i = Instance::new_async(&mut store, &m, &[]).await?;
-    let overflow = i.get_typed_func::<(), (), _>(&mut store, "overflow")?;
-    let normal = i.get_typed_func::<(), (), _>(&mut store, "normal")?;
+    let overflow = i.get_typed_func::<(), ()>(&mut store, "overflow")?;
+    let normal = i.get_typed_func::<(), ()>(&mut store, "normal")?;
     let f2 = Func::wrap0_async(&mut store, move |mut caller| {
         Box::new(async move {
             // recursive async calls shouldn't immediately stack overflow...
@@ -600,7 +600,7 @@ async fn linker_module_command() -> Result<()> {
 
     linker.module_async(&mut store, "", &module1).await?;
     let instance = linker.instantiate_async(&mut store, &module2).await?;
-    let f = instance.get_typed_func::<(), i32, _>(&mut store, "get")?;
+    let f = instance.get_typed_func::<(), i32>(&mut store, "get")?;
     assert_eq!(f.call_async(&mut store, ()).await?, 0);
     assert_eq!(f.call_async(&mut store, ()).await?, 0);
 
@@ -638,7 +638,7 @@ async fn linker_module_reactor() -> Result<()> {
 
     linker.module_async(&mut store, "", &module1).await?;
     let instance = linker.instantiate_async(&mut store, &module2).await?;
-    let f = instance.get_typed_func::<(), i32, _>(&mut store, "get")?;
+    let f = instance.get_typed_func::<(), i32>(&mut store, "get")?;
     assert_eq!(f.call_async(&mut store, ()).await?, 0);
     assert_eq!(f.call_async(&mut store, ()).await?, 1);
 
diff --git a/tests/all/call_hook.rs b/tests/all/call_hook.rs
index f28771a2d..7184f06db 100644
--- a/tests/all/call_hook.rs
+++ b/tests/all/call_hook.rs
@@ -76,7 +76,7 @@ fn call_wrapped_func() -> Result<(), Error> {
         assert_eq!(store.data().calls_into_wasm, n);
         assert_eq!(store.data().returns_from_wasm, n);
 
-        f.typed::<(i32, i64, f32, f64), (), _>(&store)?
+        f.typed::<(i32, i64, f32, f64), ()>(&store)?
             .call(&mut store, (1, 2, 3.0, 4.0))?;
         n += 1;
 
@@ -150,7 +150,7 @@ async fn call_wrapped_async_func() -> Result<(), Error> {
     assert_eq!(store.data().calls_into_wasm, 1);
     assert_eq!(store.data().returns_from_wasm, 1);
 
-    f.typed::<(i32, i64, f32, f64), (), _>(&store)?
+    f.typed::<(i32, i64, f32, f64), ()>(&store)?
         .call_async(&mut store, (1, 2, 3.0, 4.0))
         .await?;
 
@@ -218,7 +218,7 @@ fn call_linked_func() -> Result<(), Error> {
     assert_eq!(store.data().calls_into_wasm, 1);
     assert_eq!(store.data().returns_from_wasm, 1);
 
-    export.typed::<(), (), _>(&store)?.call(&mut store, ())?;
+    export.typed::<(), ()>(&store)?.call(&mut store, ())?;
 
     assert_eq!(store.data().calls_into_host, 2);
     assert_eq!(store.data().returns_from_host, 2);
@@ -290,7 +290,7 @@ async fn call_linked_func_async() -> Result<(), Error> {
     assert_eq!(store.data().returns_from_wasm, 1);
 
     export
-        .typed::<(), (), _>(&store)?
+        .typed::<(), ()>(&store)?
         .call_async(&mut store, ())
         .await?;
 
@@ -362,7 +362,7 @@ fn recursion() -> Result<(), Error> {
                 .expect("caller exports \"export\"")
                 .into_func()
                 .expect("export is a func")
-                .typed::<i32, (), _>(&caller)
+                .typed::<i32, ()>(&caller)
                 .expect("export typing")
                 .call(&mut caller, n - 1)
                 .unwrap()
@@ -398,7 +398,7 @@ fn recursion() -> Result<(), Error> {
     assert_eq!(store.data().returns_from_wasm, n + 1);
 
     export
-        .typed::<i32, (), _>(&store)?
+        .typed::<i32, ()>(&store)?
         .call(&mut store, n as i32)?;
 
     assert_eq!(store.data().calls_into_host, 2 * (n + 1));
@@ -445,7 +445,7 @@ fn trapping() -> Result<(), Error> {
                     .expect("caller exports \"export\"")
                     .into_func()
                     .expect("export is a func")
-                    .typed::<(i32, i32), (), _>(&caller)
+                    .typed::<(i32, i32), ()>(&caller)
                     .expect("export typing")
                     .call(&mut caller, (action, 0))?;
             }
@@ -676,7 +676,7 @@ async fn timeout_async_hook() -> Result<(), Error> {
 
     let inst = linker.instantiate_async(&mut store, &module).await?;
     let export = inst
-        .get_typed_func::<(), (), _>(&mut store, "export")
+        .get_typed_func::<(), ()>(&mut store, "export")
         .expect("export is func");
 
     store.set_epoch_deadline(1);
@@ -743,7 +743,7 @@ async fn drop_suspended_async_hook() -> Result<(), Error> {
     let inst = linker.instantiate_async(&mut store, &module).await?;
     assert_eq!(*store.data(), 0);
     let export = inst
-        .get_typed_func::<(), (), _>(&mut store, "")
+        .get_typed_func::<(), ()>(&mut store, "")
         .expect("export is func");
 
     // First test that if we drop in the middle of an async hook that everything
diff --git a/tests/all/custom_signal_handler.rs b/tests/all/custom_signal_handler.rs
index 22cf65416..498014093 100644
--- a/tests/all/custom_signal_handler.rs
+++ b/tests/all/custom_signal_handler.rs
@@ -47,7 +47,7 @@ mod tests {
 
     fn invoke_export(store: &mut Store<()>, instance: Instance, func_name: &str) -> Result<i32> {
         let ret = instance
-            .get_typed_func::<(), i32, _>(&mut *store, func_name)?
+            .get_typed_func::<(), i32>(&mut *store, func_name)?
             .call(store, ())?;
         Ok(ret)
     }
@@ -175,7 +175,7 @@ mod tests {
 
         // these invoke wasmtime_call_trampoline from callable.rs
         {
-            let read_func = instance.get_typed_func::<(), i32, _>(&mut store, "read")?;
+            let read_func = instance.get_typed_func::<(), i32>(&mut store, "read")?;
             println!("calling read...");
             let result = read_func
                 .call(&mut store, ())
@@ -185,7 +185,7 @@ mod tests {
 
         {
             let read_out_of_bounds_func =
-                instance.get_typed_func::<(), i32, _>(&mut store, "read_out_of_bounds")?;
+                instance.get_typed_func::<(), i32>(&mut store, "read_out_of_bounds")?;
             println!("calling read_out_of_bounds...");
             let trap = read_out_of_bounds_func
                 .call(&mut store, ())
diff --git a/tests/all/externals.rs b/tests/all/externals.rs
index 04742fa50..baf15c9d1 100644
--- a/tests/all/externals.rs
+++ b/tests/all/externals.rs
@@ -133,8 +133,8 @@ fn cross_store() -> anyhow::Result<()> {
         .call(&mut store2, &[Some(s2_f.clone()).into()], &mut [])
         .is_ok());
 
-    let s1_f_t = s1_f.typed::<Option<Func>, (), _>(&store1)?;
-    let s2_f_t = s2_f.typed::<Option<Func>, (), _>(&store2)?;
+    let s1_f_t = s1_f.typed::<Option<Func>, ()>(&store1)?;
+    let s2_f_t = s2_f.typed::<Option<Func>, ()>(&store2)?;
 
     assert!(s1_f_t.call(&mut store1, None).is_ok());
     assert!(s2_f_t.call(&mut store2, None).is_ok());
diff --git a/tests/all/fuel.rs b/tests/all/fuel.rs
index a42c3084d..eec8f9dff 100644
--- a/tests/all/fuel.rs
+++ b/tests/all/fuel.rs
@@ -170,9 +170,7 @@ fn host_function_consumes_all() {
     });
 
     let instance = Instance::new(&mut store, &module, &[func.into()]).unwrap();
-    let export = instance
-        .get_typed_func::<(), (), _>(&mut store, "")
-        .unwrap();
+    let export = instance.get_typed_func::<(), ()>(&mut store, "").unwrap();
     let trap = export.call(&mut store, ()).unwrap_err();
     assert!(
         format!("{trap:?}").contains("all fuel consumed"),
diff --git a/tests/all/func.rs b/tests/all/func.rs
index d56d44e50..30bc0c95b 100644
--- a/tests/all/func.rs
+++ b/tests/all/func.rs
@@ -246,40 +246,40 @@ fn trap_import() -> Result<()> {
 fn get_from_wrapper() {
     let mut store = Store::<()>::default();
     let f = Func::wrap(&mut store, || {});
-    assert!(f.typed::<(), (), _>(&store).is_ok());
-    assert!(f.typed::<(), i32, _>(&store).is_err());
-    assert!(f.typed::<(), (), _>(&store).is_ok());
-    assert!(f.typed::<i32, (), _>(&store).is_err());
-    assert!(f.typed::<i32, i32, _>(&store).is_err());
-    assert!(f.typed::<(i32, i32), (), _>(&store).is_err());
-    assert!(f.typed::<(i32, i32), i32, _>(&store).is_err());
+    assert!(f.typed::<(), ()>(&store).is_ok());
+    assert!(f.typed::<(), i32>(&store).is_err());
+    assert!(f.typed::<(), ()>(&store).is_ok());
+    assert!(f.typed::<i32, ()>(&store).is_err());
+    assert!(f.typed::<i32, i32>(&store).is_err());
+    assert!(f.typed::<(i32, i32), ()>(&store).is_err());
+    assert!(f.typed::<(i32, i32), i32>(&store).is_err());
 
     let f = Func::wrap(&mut store, || -> i32 { loop {} });
-    assert!(f.typed::<(), i32, _>(&store).is_ok());
+    assert!(f.typed::<(), i32>(&store).is_ok());
     let f = Func::wrap(&mut store, || -> f32 { loop {} });
-    assert!(f.typed::<(), f32, _>(&store).is_ok());
+    assert!(f.typed::<(), f32>(&store).is_ok());
     let f = Func::wrap(&mut store, || -> f64 { loop {} });
-    assert!(f.typed::<(), f64, _>(&store).is_ok());
+    assert!(f.typed::<(), f64>(&store).is_ok());
     let f = Func::wrap(&mut store, || -> Option<ExternRef> { loop {} });
-    assert!(f.typed::<(), Option<ExternRef>, _>(&store).is_ok());
+    assert!(f.typed::<(), Option<ExternRef>>(&store).is_ok());
     let f = Func::wrap(&mut store, || -> Option<Func> { loop {} });
-    assert!(f.typed::<(), Option<Func>, _>(&store).is_ok());
+    assert!(f.typed::<(), Option<Func>>(&store).is_ok());
 
     let f = Func::wrap(&mut store, |_: i32| {});
-    assert!(f.typed::<i32, (), _>(&store).is_ok());
-    assert!(f.typed::<i64, (), _>(&store).is_err());
-    assert!(f.typed::<f32, (), _>(&store).is_err());
-    assert!(f.typed::<f64, (), _>(&store).is_err());
+    assert!(f.typed::<i32, ()>(&store).is_ok());
+    assert!(f.typed::<i64, ()>(&store).is_err());
+    assert!(f.typed::<f32, ()>(&store).is_err());
+    assert!(f.typed::<f64, ()>(&store).is_err());
     let f = Func::wrap(&mut store, |_: i64| {});
-    assert!(f.typed::<i64, (), _>(&store).is_ok());
+    assert!(f.typed::<i64, ()>(&store).is_ok());
     let f = Func::wrap(&mut store, |_: f32| {});
-    assert!(f.typed::<f32, (), _>(&store).is_ok());
+    assert!(f.typed::<f32, ()>(&store).is_ok());
     let f = Func::wrap(&mut store, |_: f64| {});
-    assert!(f.typed::<f64, (), _>(&store).is_ok());
+    assert!(f.typed::<f64, ()>(&store).is_ok());
     let f = Func::wrap(&mut store, |_: Option<ExternRef>| {});
-    assert!(f.typed::<Option<ExternRef>, (), _>(&store).is_ok());
+    assert!(f.typed::<Option<ExternRef>, ()>(&store).is_ok());
     let f = Func::wrap(&mut store, |_: Option<Func>| {});
-    assert!(f.typed::<Option<Func>, (), _>(&store).is_ok());
+    assert!(f.typed::<Option<Func>, ()>(&store).is_ok());
 }
 
 #[test]
@@ -287,16 +287,16 @@ fn get_from_signature() {
     let mut store = Store::<()>::default();
     let ty = FuncType::new(None, None);
     let f = Func::new(&mut store, ty, |_, _, _| panic!());
-    assert!(f.typed::<(), (), _>(&store).is_ok());
-    assert!(f.typed::<(), i32, _>(&store).is_err());
-    assert!(f.typed::<i32, (), _>(&store).is_err());
+    assert!(f.typed::<(), ()>(&store).is_ok());
+    assert!(f.typed::<(), i32>(&store).is_err());
+    assert!(f.typed::<i32, ()>(&store).is_err());
 
     let ty = FuncType::new(Some(ValType::I32), Some(ValType::F64));
     let f = Func::new(&mut store, ty, |_, _, _| panic!());
-    assert!(f.typed::<(), (), _>(&store).is_err());
-    assert!(f.typed::<(), i32, _>(&store).is_err());
-    assert!(f.typed::<i32, (), _>(&store).is_err());
-    assert!(f.typed::<i32, f64, _>(&store).is_ok());
+    assert!(f.typed::<(), ()>(&store).is_err());
+    assert!(f.typed::<(), i32>(&store).is_err());
+    assert!(f.typed::<i32, ()>(&store).is_err());
+    assert!(f.typed::<i32, f64>(&store).is_ok());
 }
 
 #[test]
@@ -316,17 +316,17 @@ fn get_from_module() -> anyhow::Result<()> {
     )?;
     let instance = Instance::new(&mut store, &module, &[])?;
     let f0 = instance.get_func(&mut store, "f0").unwrap();
-    assert!(f0.typed::<(), (), _>(&store).is_ok());
-    assert!(f0.typed::<(), i32, _>(&store).is_err());
+    assert!(f0.typed::<(), ()>(&store).is_ok());
+    assert!(f0.typed::<(), i32>(&store).is_err());
     let f1 = instance.get_func(&mut store, "f1").unwrap();
-    assert!(f1.typed::<(), (), _>(&store).is_err());
-    assert!(f1.typed::<i32, (), _>(&store).is_ok());
-    assert!(f1.typed::<i32, f32, _>(&store).is_err());
+    assert!(f1.typed::<(), ()>(&store).is_err());
+    assert!(f1.typed::<i32, ()>(&store).is_ok());
+    assert!(f1.typed::<i32, f32>(&store).is_err());
     let f2 = instance.get_func(&mut store, "f2").unwrap();
-    assert!(f2.typed::<(), (), _>(&store).is_err());
-    assert!(f2.typed::<(), i32, _>(&store).is_ok());
-    assert!(f2.typed::<i32, (), _>(&store).is_err());
-    assert!(f2.typed::<i32, f32, _>(&store).is_err());
+    assert!(f2.typed::<(), ()>(&store).is_err());
+    assert!(f2.typed::<(), i32>(&store).is_ok());
+    assert!(f2.typed::<i32, ()>(&store).is_err());
+    assert!(f2.typed::<i32, f32>(&store).is_err());
     Ok(())
 }
 
@@ -344,29 +344,29 @@ fn call_wrapped_func() -> Result<()> {
         &[Val::I32(1), Val::I64(2), 3.0f32.into(), 4.0f64.into()],
         &mut [],
     )?;
-    f.typed::<(i32, i64, f32, f64), (), _>(&store)?
+    f.typed::<(i32, i64, f32, f64), ()>(&store)?
         .call(&mut store, (1, 2, 3.0, 4.0))?;
 
     let mut results = [Val::I32(0)];
     let f = Func::wrap(&mut store, || 1i32);
     f.call(&mut store, &[], &mut results)?;
     assert_eq!(results[0].unwrap_i32(), 1);
-    assert_eq!(f.typed::<(), i32, _>(&store)?.call(&mut store, ())?, 1);
+    assert_eq!(f.typed::<(), i32>(&store)?.call(&mut store, ())?, 1);
 
     let f = Func::wrap(&mut store, || 2i64);
     f.call(&mut store, &[], &mut results)?;
     assert_eq!(results[0].unwrap_i64(), 2);
-    assert_eq!(f.typed::<(), i64, _>(&store)?.call(&mut store, ())?, 2);
+    assert_eq!(f.typed::<(), i64>(&store)?.call(&mut store, ())?, 2);
 
     let f = Func::wrap(&mut store, || 3.0f32);
     f.call(&mut store, &[], &mut results)?;
     assert_eq!(results[0].unwrap_f32(), 3.0);
-    assert_eq!(f.typed::<(), f32, _>(&store)?.call(&mut store, ())?, 3.0);
+    assert_eq!(f.typed::<(), f32>(&store)?.call(&mut store, ())?, 3.0);
 
     let f = Func::wrap(&mut store, || 4.0f64);
     f.call(&mut store, &[], &mut results)?;
     assert_eq!(results[0].unwrap_f64(), 4.0);
-    assert_eq!(f.typed::<(), f64, _>(&store)?.call(&mut store, ())?, 4.0);
+    assert_eq!(f.typed::<(), f64>(&store)?.call(&mut store, ())?, 4.0);
     Ok(())
 }
 
@@ -502,7 +502,7 @@ fn pass_cross_store_arg() -> anyhow::Result<()> {
 
     // And using `.get` followed by a function call also fails with cross-Store
     // arguments.
-    let f = store1_func.typed::<Option<Func>, (), _>(&store1)?;
+    let f = store1_func.typed::<Option<Func>, ()>(&store1)?;
     let result = f.call(&mut store1, Some(store2_func));
     assert!(result.is_err());
     assert!(result.unwrap_err().to_string().contains("cross-`Store`"));
@@ -572,18 +572,17 @@ fn typed_multiple_results() -> anyhow::Result<()> {
     )?;
     let instance = Instance::new(&mut store, &module, &[])?;
     let f0 = instance.get_func(&mut store, "f0").unwrap();
-    assert!(f0.typed::<(), (), _>(&store).is_err());
-    assert!(f0.typed::<(), (i32, f32), _>(&store).is_err());
-    assert!(f0.typed::<(), i32, _>(&store).is_err());
+    assert!(f0.typed::<(), ()>(&store).is_err());
+    assert!(f0.typed::<(), (i32, f32)>(&store).is_err());
+    assert!(f0.typed::<(), i32>(&store).is_err());
     assert_eq!(
-        f0.typed::<(), (i32, i64), _>(&store)?
-            .call(&mut store, ())?,
+        f0.typed::<(), (i32, i64)>(&store)?.call(&mut store, ())?,
         (0, 1)
     );
 
     let f1 = instance.get_func(&mut store, "f1").unwrap();
     assert_eq!(
-        f1.typed::<(i32, i32, i32), (f32, f64), _>(&store)?
+        f1.typed::<(i32, i32, i32), (f32, f64)>(&store)?
             .call(&mut store, (1, 2, 3))?,
         (2., 3.)
     );
@@ -610,7 +609,7 @@ fn trap_doesnt_leak() -> anyhow::Result<()> {
         drop(&canary1);
         bail!("")
     });
-    assert!(f1.typed::<(), (), _>(&store)?.call(&mut store, ()).is_err());
+    assert!(f1.typed::<(), ()>(&store)?.call(&mut store, ()).is_err());
     assert!(f1.call(&mut store, &[], &mut []).is_err());
 
     // test that `Func::new` is correct
@@ -620,7 +619,7 @@ fn trap_doesnt_leak() -> anyhow::Result<()> {
         drop(&canary2);
         bail!("")
     });
-    assert!(f2.typed::<(), (), _>(&store)?.call(&mut store, ()).is_err());
+    assert!(f2.typed::<(), ()>(&store)?.call(&mut store, ()).is_err());
     assert!(f2.call(&mut store, &[], &mut []).is_err());
 
     // drop everything and ensure dtors are run
@@ -646,7 +645,7 @@ fn wrap_multiple_results() -> anyhow::Result<()> {
     {
         let f = Func::wrap(&mut *store, move || t);
         let mut results = vec![Val::I32(0); f.ty(&store).results().len()];
-        assert_eq!(f.typed::<(), T, _>(&store)?.call(&mut *store, ())?, t);
+        assert_eq!(f.typed::<(), T>(&store)?.call(&mut *store, ())?, t);
         f.call(&mut *store, &[], &mut results)?;
         assert!(t.eq_values(&results));
 
@@ -654,7 +653,7 @@ fn wrap_multiple_results() -> anyhow::Result<()> {
         let instance = Instance::new(&mut *store, &module, &[f.into()])?;
         let f = instance.get_func(&mut *store, "foo").unwrap();
 
-        assert_eq!(f.typed::<(), T, _>(&store)?.call(&mut *store, ())?, t);
+        assert_eq!(f.typed::<(), T>(&store)?.call(&mut *store, ())?, t);
         f.call(&mut *store, &[], &mut results)?;
         assert!(t.eq_values(&results));
         Ok(())
@@ -814,7 +813,7 @@ fn trampoline_for_declared_elem() -> anyhow::Result<()> {
     let mut store = Store::new(&engine, ());
     let instance = Instance::new(&mut store, &module, &[])?;
 
-    let g = instance.get_typed_func::<(), Option<Func>, _>(&mut store, "g")?;
+    let g = instance.get_typed_func::<(), Option<Func>>(&mut store, "g")?;
 
     let func = g.call(&mut store, ())?;
     func.unwrap().call(&mut store, &[], &mut [])?;
@@ -871,8 +870,7 @@ fn wasm_ty_roundtrip() -> Result<(), anyhow::Error> {
          "#,
     )?;
     let instance = Instance::new(&mut store, &module, &[debug.into()])?;
-    let foo =
-        instance.get_typed_func::<(i32, u32, f32, i64, u64, f64), (), _>(&mut store, "foo")?;
+    let foo = instance.get_typed_func::<(i32, u32, f32, i64, u64, f64), ()>(&mut store, "foo")?;
     foo.call(&mut store, (-1, 1, 2.0, -3, 3, 4.0))?;
     Ok(())
 }
@@ -892,14 +890,14 @@ fn typed_funcs_count_params_correctly_in_error_messages() -> anyhow::Result<()>
     let instance = Instance::new(&mut store, &module, &[])?;
 
     // Too few parameters.
-    match instance.get_typed_func::<(), (), _>(&mut store, "f") {
+    match instance.get_typed_func::<(), ()>(&mut store, "f") {
         Ok(_) => panic!("should be wrong signature"),
         Err(e) => {
             let msg = format!("{:?}", e);
             assert!(dbg!(msg).contains("expected 0 types, found 2"))
         }
     }
-    match instance.get_typed_func::<(i32,), (), _>(&mut store, "f") {
+    match instance.get_typed_func::<(i32,), ()>(&mut store, "f") {
         Ok(_) => panic!("should be wrong signature"),
         Err(e) => {
             let msg = format!("{:?}", e);
@@ -908,7 +906,7 @@ fn typed_funcs_count_params_correctly_in_error_messages() -> anyhow::Result<()>
     }
 
     // Too many parameters.
-    match instance.get_typed_func::<(i32, i32, i32), (), _>(&mut store, "f") {
+    match instance.get_typed_func::<(i32, i32, i32), ()>(&mut store, "f") {
         Ok(_) => panic!("should be wrong signature"),
         Err(e) => {
             let msg = format!("{:?}", e);
diff --git a/tests/all/gc.rs b/tests/all/gc.rs
index 4730f7941..e9b1e0abd 100644
--- a/tests/all/gc.rs
+++ b/tests/all/gc.rs
@@ -285,7 +285,7 @@ fn global_drops_externref() -> anyhow::Result<()> {
             "#,
         )?;
         let instance = Instance::new(&mut store, &module, &[])?;
-        let run = instance.get_typed_func::<Option<ExternRef>, (), _>(&mut store, "run")?;
+        let run = instance.get_typed_func::<Option<ExternRef>, ()>(&mut store, "run")?;
         let flag = Arc::new(AtomicBool::new(false));
         let externref = ExternRef::new(SetFlagOnDrop(flag.clone()));
         run.call(&mut store, Some(externref))?;
@@ -335,7 +335,7 @@ fn table_drops_externref() -> anyhow::Result<()> {
         "#,
         )?;
         let instance = Instance::new(&mut store, &module, &[])?;
-        let run = instance.get_typed_func::<Option<ExternRef>, (), _>(&mut store, "run")?;
+        let run = instance.get_typed_func::<Option<ExternRef>, ()>(&mut store, "run")?;
         let flag = Arc::new(AtomicBool::new(false));
         let externref = ExternRef::new(SetFlagOnDrop(flag.clone()));
         run.call(&mut store, Some(externref))?;
@@ -387,7 +387,7 @@ fn gee_i_sure_hope_refcounting_is_atomic() -> anyhow::Result<()> {
     )?;
 
     let instance = Instance::new(&mut store, &module, &[])?;
-    let run = instance.get_typed_func::<Option<ExternRef>, (), _>(&mut store, "run")?;
+    let run = instance.get_typed_func::<Option<ExternRef>, ()>(&mut store, "run")?;
 
     let flag = Arc::new(AtomicBool::new(false));
     let externref = ExternRef::new(SetFlagOnDrop(flag.clone()));
@@ -482,7 +482,7 @@ fn no_gc_middle_of_args() -> anyhow::Result<()> {
     )?;
 
     let instance = linker.instantiate(&mut store, &module)?;
-    let func = instance.get_typed_func::<(), (), _>(&mut store, "run")?;
+    let func = instance.get_typed_func::<(), ()>(&mut store, "run")?;
     func.call(&mut store, ())?;
 
     Ok(())
diff --git a/tests/all/host_funcs.rs b/tests/all/host_funcs.rs
index 9b96b69ee..795305efb 100644
--- a/tests/all/host_funcs.rs
+++ b/tests/all/host_funcs.rs
@@ -430,7 +430,7 @@ fn call_wasm_many_args() -> Result<()> {
     )?;
 
     let typed_run = instance
-        .get_typed_func::<(i32, i32, i32, i32, i32, i32, i32, i32, i32, i32), (), _>(
+        .get_typed_func::<(i32, i32, i32, i32, i32, i32, i32, i32, i32, i32), ()>(
             &mut store, "run",
         )?;
     typed_run.call(&mut store, (1, 2, 3, 4, 5, 6, 7, 8, 9, 10))?;
@@ -499,19 +499,19 @@ fn new_from_signature() -> Result<()> {
         .unwrap()
         .into_func()
         .unwrap();
-    assert!(f.typed::<(), (), _>(&store).is_ok());
-    assert!(f.typed::<(), i32, _>(&store).is_err());
-    assert!(f.typed::<i32, (), _>(&store).is_err());
+    assert!(f.typed::<(), ()>(&store).is_ok());
+    assert!(f.typed::<(), i32>(&store).is_err());
+    assert!(f.typed::<i32, ()>(&store).is_err());
 
     let f = linker
         .get(&mut store, "", "f2")
         .unwrap()
         .into_func()
         .unwrap();
-    assert!(f.typed::<(), (), _>(&store).is_err());
-    assert!(f.typed::<(), i32, _>(&store).is_err());
-    assert!(f.typed::<i32, (), _>(&store).is_err());
-    assert!(f.typed::<i32, f64, _>(&store).is_ok());
+    assert!(f.typed::<(), ()>(&store).is_err());
+    assert!(f.typed::<(), i32>(&store).is_err());
+    assert!(f.typed::<i32, ()>(&store).is_err());
+    assert!(f.typed::<i32, f64>(&store).is_ok());
 
     Ok(())
 }
@@ -549,7 +549,7 @@ fn call_wrapped_func() -> Result<()> {
         &[Val::I32(1), Val::I64(2), 3.0f32.into(), 4.0f64.into()],
         &mut [],
     )?;
-    f.typed::<(i32, i64, f32, f64), (), _>(&store)?
+    f.typed::<(i32, i64, f32, f64), ()>(&store)?
         .call(&mut store, (1, 2, 3.0, 4.0))?;
 
     let f = linker
@@ -559,7 +559,7 @@ fn call_wrapped_func() -> Result<()> {
         .unwrap();
     f.call(&mut store, &[], &mut results)?;
     assert_eq!(results[0].unwrap_i32(), 1);
-    assert_eq!(f.typed::<(), i32, _>(&store)?.call(&mut store, ())?, 1);
+    assert_eq!(f.typed::<(), i32>(&store)?.call(&mut store, ())?, 1);
 
     let f = linker
         .get(&mut store, "", "f3")
@@ -568,7 +568,7 @@ fn call_wrapped_func() -> Result<()> {
         .unwrap();
     f.call(&mut store, &[], &mut results)?;
     assert_eq!(results[0].unwrap_i64(), 2);
-    assert_eq!(f.typed::<(), i64, _>(&store)?.call(&mut store, ())?, 2);
+    assert_eq!(f.typed::<(), i64>(&store)?.call(&mut store, ())?, 2);
 
     let f = linker
         .get(&mut store, "", "f4")
@@ -577,7 +577,7 @@ fn call_wrapped_func() -> Result<()> {
         .unwrap();
     f.call(&mut store, &[], &mut results)?;
     assert_eq!(results[0].unwrap_f32(), 3.0);
-    assert_eq!(f.typed::<(), f32, _>(&store)?.call(&mut store, ())?, 3.0);
+    assert_eq!(f.typed::<(), f32>(&store)?.call(&mut store, ())?, 3.0);
 
     let f = linker
         .get(&mut store, "", "f5")
@@ -586,7 +586,7 @@ fn call_wrapped_func() -> Result<()> {
         .unwrap();
     f.call(&mut store, &[], &mut results)?;
     assert_eq!(results[0].unwrap_f64(), 4.0);
-    assert_eq!(f.typed::<(), f64, _>(&store)?.call(&mut store, ())?, 4.0);
+    assert_eq!(f.typed::<(), f64>(&store)?.call(&mut store, ())?, 4.0);
 
     Ok(())
 }
@@ -714,7 +714,7 @@ fn wasi_imports() -> Result<()> {
     let mut store = Store::new(&engine, WasiCtxBuilder::new().build());
     let instance = linker.instantiate(&mut store, &module)?;
 
-    let start = instance.get_typed_func::<(), (), _>(&mut store, "_start")?;
+    let start = instance.get_typed_func::<(), ()>(&mut store, "_start")?;
     let exit = start
         .call(&mut store, ())
         .unwrap_err()
diff --git a/tests/all/iloop.rs b/tests/all/iloop.rs
index 68614ddd7..fedef6f6d 100644
--- a/tests/all/iloop.rs
+++ b/tests/all/iloop.rs
@@ -29,7 +29,7 @@ fn loops_interruptable() -> anyhow::Result<()> {
     let mut store = interruptable_store();
     let module = Module::new(store.engine(), r#"(func (export "loop") (loop br 0))"#)?;
     let instance = Instance::new(&mut store, &module, &[])?;
-    let iloop = instance.get_typed_func::<(), (), _>(&mut store, "loop")?;
+    let iloop = instance.get_typed_func::<(), ()>(&mut store, "loop")?;
     store.engine().increment_epoch();
     let trap = iloop.call(&mut store, ()).unwrap_err().downcast::<Trap>()?;
     assert_eq!(trap, Trap::Interrupt);
@@ -42,7 +42,7 @@ fn functions_interruptable() -> anyhow::Result<()> {
     let module = hugely_recursive_module(store.engine())?;
     let func = Func::wrap(&mut store, || {});
     let instance = Instance::new(&mut store, &module, &[func.into()])?;
-    let iloop = instance.get_typed_func::<(), (), _>(&mut store, "loop")?;
+    let iloop = instance.get_typed_func::<(), ()>(&mut store, "loop")?;
     store.engine().increment_epoch();
     let trap = iloop.call(&mut store, ()).unwrap_err().downcast::<Trap>()?;
     assert_eq!(trap, Trap::Interrupt);
@@ -89,7 +89,7 @@ fn loop_interrupt_from_afar() -> anyhow::Result<()> {
 
     // Enter the infinitely looping function and assert that our interrupt
     // handle does indeed actually interrupt the function.
-    let iloop = instance.get_typed_func::<(), (), _>(&mut store, "loop")?;
+    let iloop = instance.get_typed_func::<(), ()>(&mut store, "loop")?;
     let trap = iloop.call(&mut store, ()).unwrap_err().downcast::<Trap>()?;
     STOP.store(true, SeqCst);
     thread.join().unwrap();
@@ -125,7 +125,7 @@ fn function_interrupt_from_afar() -> anyhow::Result<()> {
 
     // Enter the infinitely looping function and assert that our interrupt
     // handle does indeed actually interrupt the function.
-    let iloop = instance.get_typed_func::<(), (), _>(&mut store, "loop")?;
+    let iloop = instance.get_typed_func::<(), ()>(&mut store, "loop")?;
     let trap = iloop.call(&mut store, ()).unwrap_err().downcast::<Trap>()?;
     STOP.store(true, SeqCst);
     thread.join().unwrap();
diff --git a/tests/all/import_indexes.rs b/tests/all/import_indexes.rs
index 94c58639b..d6718f166 100644
--- a/tests/all/import_indexes.rs
+++ b/tests/all/import_indexes.rs
@@ -43,7 +43,7 @@ fn same_import_names_still_distinct() -> anyhow::Result<()> {
     ];
     let instance = Instance::new(&mut store, &module, &imports)?;
 
-    let func = instance.get_typed_func::<(), i32, _>(&mut store, "foo")?;
+    let func = instance.get_typed_func::<(), i32>(&mut store, "foo")?;
     let result = func.call(&mut store, ())?;
     assert_eq!(result, 3);
     Ok(())
diff --git a/tests/all/instance.rs b/tests/all/instance.rs
index e37dd95a9..95ed175df 100644
--- a/tests/all/instance.rs
+++ b/tests/all/instance.rs
@@ -63,8 +63,8 @@ fn linear_memory_limits() -> Result<()> {
 
         let mut store = Store::new(engine, ());
         let instance = Instance::new(&mut store, &module, &[])?;
-        let size = instance.get_typed_func::<(), i32, _>(&mut store, "size")?;
-        let grow = instance.get_typed_func::<(), i32, _>(&mut store, "grow")?;
+        let size = instance.get_typed_func::<(), i32>(&mut store, "size")?;
+        let grow = instance.get_typed_func::<(), i32>(&mut store, "grow")?;
 
         assert_eq!(size.call(&mut store, ())?, 65534);
         assert_eq!(grow.call(&mut store, ())?, 65534);
diff --git a/tests/all/limits.rs b/tests/all/limits.rs
index d1b013cb1..333583f65 100644
--- a/tests/all/limits.rs
+++ b/tests/all/limits.rs
@@ -78,7 +78,7 @@ fn test_limits() -> Result<()> {
     store.limiter(|s| s as &mut dyn ResourceLimiter);
     let instance = Instance::new(&mut store, &module, &[])?;
     let grow = instance.get_func(&mut store, "grow").unwrap();
-    let grow = grow.typed::<i32, i32, _>(&store).unwrap();
+    let grow = grow.typed::<i32, i32>(&store).unwrap();
 
     grow.call(&mut store, 3).unwrap();
     grow.call(&mut store, 5).unwrap();
@@ -464,7 +464,7 @@ fn test_custom_memory_limiter() -> Result<()> {
     assert!(!store.data().limit_exceeded);
 
     // Grow the host "memory" by 384 KiB
-    let f = instance.get_typed_func::<u32, u32, _>(&mut store, "f")?;
+    let f = instance.get_typed_func::<u32, u32>(&mut store, "f")?;
 
     assert_eq!(f.call(&mut store, 1 * 0x10000)?, 1);
     assert_eq!(f.call(&mut store, 3 * 0x10000)?, 1);
@@ -576,7 +576,7 @@ async fn test_custom_memory_limiter_async() -> Result<()> {
     assert!(!store.data().limit_exceeded);
 
     // Grow the host "memory" by 384 KiB
-    let f = instance.get_typed_func::<u32, u32, _>(&mut store, "f")?;
+    let f = instance.get_typed_func::<u32, u32>(&mut store, "f")?;
 
     assert_eq!(f.call_async(&mut store, 1 * 0x10000).await?, 1);
     assert_eq!(f.call_async(&mut store, 3 * 0x10000).await?, 1);
@@ -965,7 +965,7 @@ fn panic_in_memory_limiter_wasm_stack() {
     store.limiter(|s| s as &mut dyn ResourceLimiter);
     let instance = linker.instantiate(&mut store, &module).unwrap();
     let grow = instance.get_func(&mut store, "grow").unwrap();
-    let grow = grow.typed::<i32, i32, _>(&store).unwrap();
+    let grow = grow.typed::<i32, i32>(&store).unwrap();
 
     // Grow the memory, which should panic
     grow.call(&mut store, 3).unwrap();
@@ -1032,7 +1032,7 @@ async fn panic_in_async_memory_limiter_wasm_stack() {
     store.limiter_async(|s| s as &mut dyn ResourceLimiterAsync);
     let instance = linker.instantiate_async(&mut store, &module).await.unwrap();
     let grow = instance.get_func(&mut store, "grow").unwrap();
-    let grow = grow.typed::<i32, i32, _>(&store).unwrap();
+    let grow = grow.typed::<i32, i32>(&store).unwrap();
 
     // Grow the memory, which should panic
     grow.call_async(&mut store, 3).await.unwrap();
diff --git a/tests/all/linker.rs b/tests/all/linker.rs
index 9728d757f..1f65f017b 100644
--- a/tests/all/linker.rs
+++ b/tests/all/linker.rs
@@ -101,7 +101,7 @@ fn function_interposition() -> Result<()> {
         .unwrap()
         .into_func()
         .unwrap();
-    let func = func.typed::<(), i32, _>(&store)?;
+    let func = func.typed::<(), i32>(&store)?;
     assert_eq!(func.call(&mut store, ())?, 112);
     Ok(())
 }
@@ -134,7 +134,7 @@ fn function_interposition_renamed() -> Result<()> {
     }
     let instance = linker.instantiate(&mut store, &module)?;
     let func = instance.get_func(&mut store, "export").unwrap();
-    let func = func.typed::<(), i32, _>(&store)?;
+    let func = func.typed::<(), i32>(&store)?;
     assert_eq!(func.call(&mut store, ())?, 112);
     Ok(())
 }
@@ -167,7 +167,7 @@ fn module_interposition() -> Result<()> {
         .unwrap()
         .into_func()
         .unwrap();
-    let func = func.typed::<(), i32, _>(&store)?;
+    let func = func.typed::<(), i32>(&store)?;
     assert_eq!(func.call(&mut store, ())?, 112);
     Ok(())
 }
diff --git a/tests/all/memory.rs b/tests/all/memory.rs
index 2005479f0..281a2e7ab 100644
--- a/tests/all/memory.rs
+++ b/tests/all/memory.rs
@@ -346,7 +346,7 @@ fn tiny_static_heap() -> Result<()> {
     )?;
 
     let i = Instance::new(&mut store, &module, &[])?;
-    let f = i.get_typed_func::<(), (), _>(&mut store, "run")?;
+    let f = i.get_typed_func::<(), ()>(&mut store, "run")?;
     f.call(&mut store, ())?;
     Ok(())
 }
diff --git a/tests/all/module.rs b/tests/all/module.rs
index dd5e53f59..904bc6dc4 100644
--- a/tests/all/module.rs
+++ b/tests/all/module.rs
@@ -62,7 +62,7 @@ fn aot_compiles() -> Result<()> {
     let mut store = Store::new(&engine, ());
     let instance = Instance::new(&mut store, &module, &[])?;
 
-    let f = instance.get_typed_func::<i32, i32, _>(&mut store, "f")?;
+    let f = instance.get_typed_func::<i32, i32>(&mut store, "f")?;
     assert_eq!(f.call(&mut store, 101)?, 101);
 
     Ok(())
diff --git a/tests/all/module_serialize.rs b/tests/all/module_serialize.rs
index 121afc371..614803019 100644
--- a/tests/all/module_serialize.rs
+++ b/tests/all/module_serialize.rs
@@ -51,7 +51,7 @@ fn test_module_serialize_simple() -> Result<()> {
 
     let mut store = Store::default();
     let instance = unsafe { deserialize_and_instantiate(&mut store, &buffer)? };
-    let run = instance.get_typed_func::<(), i32, _>(&mut store, "run")?;
+    let run = instance.get_typed_func::<(), i32>(&mut store, "run")?;
     let result = run.call(&mut store, ())?;
 
     assert_eq!(42, result);
@@ -98,7 +98,7 @@ fn test_deserialize_from_file() -> Result<()> {
         fs::write(&path, &buffer)?;
         let module = unsafe { Module::deserialize_file(store.engine(), &path)? };
         let instance = Instance::new(&mut store, &module, &[])?;
-        let func = instance.get_typed_func::<(), i32, _>(&mut store, "run")?;
+        let func = instance.get_typed_func::<(), i32>(&mut store, "run")?;
         assert_eq!(func.call(&mut store, ())?, 42);
         Ok(())
     }
diff --git a/tests/all/pooling_allocator.rs b/tests/all/pooling_allocator.rs
index b97ebd474..d2f38924d 100644
--- a/tests/all/pooling_allocator.rs
+++ b/tests/all/pooling_allocator.rs
@@ -67,7 +67,7 @@ fn memory_limit() -> Result<()> {
     {
         let mut store = Store::new(&engine, ());
         let instance = Instance::new(&mut store, &module, &[])?;
-        let f = instance.get_typed_func::<(), i32, _>(&mut store, "f")?;
+        let f = instance.get_typed_func::<(), i32>(&mut store, "f")?;
 
         assert_eq!(f.call(&mut store, ()).expect("function should not trap"), 0);
         assert_eq!(f.call(&mut store, ()).expect("function should not trap"), 1);
@@ -149,7 +149,7 @@ fn memory_guard_page_trap() -> Result<()> {
         let mut store = Store::new(&engine, ());
         let instance = Instance::new(&mut store, &module, &[])?;
         let m = instance.get_memory(&mut store, "m").unwrap();
-        let f = instance.get_typed_func::<i32, (), _>(&mut store, "f")?;
+        let f = instance.get_typed_func::<i32, ()>(&mut store, "f")?;
 
         let trap = f
             .call(&mut store, 0)
@@ -273,7 +273,7 @@ fn table_limit() -> Result<()> {
     {
         let mut store = Store::new(&engine, ());
         let instance = Instance::new(&mut store, &module, &[])?;
-        let f = instance.get_typed_func::<(), i32, _>(&mut store, "f")?;
+        let f = instance.get_typed_func::<(), i32>(&mut store, "f")?;
 
         for i in 0..TABLE_ELEMENTS {
             assert_eq!(
@@ -611,7 +611,7 @@ fn switch_image_and_non_image() -> Result<()> {
     let assert_zero = || -> Result<()> {
         let mut store = Store::new(&engine, ());
         let instance = Instance::new(&mut store, &module1, &[])?;
-        let func = instance.get_typed_func::<i32, i32, _>(&mut store, "load")?;
+        let func = instance.get_typed_func::<i32, i32>(&mut store, "load")?;
         assert_eq!(func.call(&mut store, 0)?, 0);
         Ok(())
     };
@@ -719,10 +719,10 @@ fn dynamic_memory_pooling_allocator() -> Result<()> {
     let mut store = Store::new(&engine, ());
     let instance = Instance::new(&mut store, &module, &[])?;
 
-    let grow = instance.get_typed_func::<u32, i32, _>(&mut store, "grow")?;
-    let size = instance.get_typed_func::<(), u32, _>(&mut store, "size")?;
-    let i32_load = instance.get_typed_func::<u32, i32, _>(&mut store, "i32.load")?;
-    let i32_store = instance.get_typed_func::<(u32, i32), (), _>(&mut store, "i32.store")?;
+    let grow = instance.get_typed_func::<u32, i32>(&mut store, "grow")?;
+    let size = instance.get_typed_func::<(), u32>(&mut store, "size")?;
+    let i32_load = instance.get_typed_func::<u32, i32>(&mut store, "i32.load")?;
+    let i32_store = instance.get_typed_func::<(u32, i32), ()>(&mut store, "i32.store")?;
     let memory = instance.get_memory(&mut store, "memory").unwrap();
 
     // basic length 1 tests
@@ -757,7 +757,7 @@ fn dynamic_memory_pooling_allocator() -> Result<()> {
     // Re-instantiate in another store.
     store = Store::new(&engine, ());
     let instance = Instance::new(&mut store, &module, &[])?;
-    let i32_load = instance.get_typed_func::<u32, i32, _>(&mut store, "i32.load")?;
+    let i32_load = instance.get_typed_func::<u32, i32>(&mut store, "i32.load")?;
     let memory = instance.get_memory(&mut store, "memory").unwrap();
 
     // Technically this is out of bounds...
@@ -806,8 +806,8 @@ fn zero_memory_pages_disallows_oob() -> Result<()> {
     )?;
     let mut store = Store::new(&engine, ());
     let instance = Instance::new(&mut store, &module, &[])?;
-    let load32 = instance.get_typed_func::<i32, i32, _>(&mut store, "load")?;
-    let store32 = instance.get_typed_func::<i32, (), _>(&mut store, "store")?;
+    let load32 = instance.get_typed_func::<i32, i32>(&mut store, "load")?;
+    let store32 = instance.get_typed_func::<i32, ()>(&mut store, "store")?;
     for i in 0..31 {
         assert!(load32.call(&mut store, 1 << i).is_err());
         assert!(store32.call(&mut store, 1 << i).is_err());
diff --git a/tests/all/relocs.rs b/tests/all/relocs.rs
index c799fe1cb..a27ff6a00 100644
--- a/tests/all/relocs.rs
+++ b/tests/all/relocs.rs
@@ -43,7 +43,7 @@ fn forward_call_works() -> Result<()> {
     )?;
 
     let i = Instance::new(&mut store, &module, &[])?;
-    let foo = i.get_typed_func::<(), i32, _>(&mut store, "foo")?;
+    let foo = i.get_typed_func::<(), i32>(&mut store, "foo")?;
     assert_eq!(foo.call(&mut store, ())?, 4);
     Ok(())
 }
@@ -64,7 +64,7 @@ fn backwards_call_works() -> Result<()> {
     )?;
 
     let i = Instance::new(&mut store, &module, &[])?;
-    let foo = i.get_typed_func::<(), i32, _>(&mut store, "foo")?;
+    let foo = i.get_typed_func::<(), i32>(&mut store, "foo")?;
     assert_eq!(foo.call(&mut store, ())?, 4);
     Ok(())
 }
@@ -108,7 +108,7 @@ fn test_many_call_module(mut store: Store<()>) -> Result<()> {
 
     for i in 0..N {
         let name = i.to_string();
-        let func = instance.get_typed_func::<(), (i32, i32), _>(&mut store, &name)?;
+        let func = instance.get_typed_func::<(), (i32, i32)>(&mut store, &name)?;
         let (a, b) = func.call(&mut store, ())?;
         assert_eq!(a, i + 1);
         assert_eq!(b, i + 2);
diff --git a/tests/all/stack_overflow.rs b/tests/all/stack_overflow.rs
index 9cd90eb9c..67ab7e22a 100644
--- a/tests/all/stack_overflow.rs
+++ b/tests/all/stack_overflow.rs
@@ -24,7 +24,7 @@ fn host_always_has_some_stack() -> anyhow::Result<()> {
     )?;
     let func = Func::wrap(&mut store, test_host_stack);
     let instance = Instance::new(&mut store, &module, &[func.into()])?;
-    let foo = instance.get_typed_func::<(), (), _>(&mut store, "foo")?;
+    let foo = instance.get_typed_func::<(), ()>(&mut store, "foo")?;
 
     // Make sure that our function traps and the trap says that the call stack
     // has been exhausted.
diff --git a/tests/all/threads.rs b/tests/all/threads.rs
index 4d05a8fd8..fe497beec 100644
--- a/tests/all/threads.rs
+++ b/tests/all/threads.rs
@@ -82,10 +82,10 @@ fn test_sharing_of_shared_memory() -> Result<()> {
         ]
     });
     let instance1_first_word = instance1
-        .get_typed_func::<(), i32, _>(&mut store, "first_word")?
+        .get_typed_func::<(), i32>(&mut store, "first_word")?
         .call(&mut store, ())?;
     let instance2_first_word = instance2
-        .get_typed_func::<(), i32, _>(&mut store, "first_word")?
+        .get_typed_func::<(), i32>(&mut store, "first_word")?
         .call(&mut store, ())?;
     assert_eq!(shared_memory_first_word, 42);
     assert_eq!(instance1_first_word, 42);
@@ -106,7 +106,7 @@ fn test_probe_shared_memory_size() -> Result<()> {
     let module = Module::new(&engine, wat)?;
     let mut store = Store::new(&engine, ());
     let instance = Instance::new(&mut store, &module, &[])?;
-    let size_fn = instance.get_typed_func::<(), i32, _>(&mut store, "size")?;
+    let size_fn = instance.get_typed_func::<(), i32>(&mut store, "size")?;
     let mut shared_memory = instance.get_shared_memory(&mut store, "memory").unwrap();
 
     assert_eq!(size_fn.call(&mut store, ())?, 1);
@@ -185,7 +185,7 @@ fn test_grow_memory_in_multiple_threads() -> Result<()> {
             let mut store = Store::new(&engine, ());
             let instance = Instance::new(&mut store, &module, &[shared_memory.into()]).unwrap();
             let grow_fn = instance
-                .get_typed_func::<i32, i32, _>(&mut store, "grow")
+                .get_typed_func::<i32, i32>(&mut store, "grow")
                 .unwrap();
             let mut thread_local_observed_sizes: Vec<_> = (0..NUM_GROW_OPS / NUM_THREADS)
                 .map(|_| grow_fn.call(&mut store, 1).unwrap() as u32)
@@ -260,7 +260,7 @@ fn test_memory_size_accessibility() -> Result<()> {
         let mut store = Store::new(&engine, ());
         let instance = Instance::new(&mut store, &module, &[probe_memory.into()]).unwrap();
         let probe_fn = instance
-            .get_typed_func::<(), i32, _>(&mut store, "probe_last_available")
+            .get_typed_func::<(), i32>(&mut store, "probe_last_available")
             .unwrap();
         while !probe_done.load(Ordering::SeqCst) {
             let value = probe_fn.call(&mut store, ()).unwrap() as u32;
diff --git a/tests/all/traps.rs b/tests/all/traps.rs
index c01ddb88e..f1776c875 100644
--- a/tests/all/traps.rs
+++ b/tests/all/traps.rs
@@ -18,7 +18,7 @@ fn test_trap_return() -> Result<()> {
     let hello_func = Func::new(&mut store, hello_type, |_, _, _| bail!("test 123"));
 
     let instance = Instance::new(&mut store, &module, &[hello_func.into()])?;
-    let run_func = instance.get_typed_func::<(), (), _>(&mut store, "run")?;
+    let run_func = instance.get_typed_func::<(), ()>(&mut store, "run")?;
 
     let e = run_func.call(&mut store, ()).unwrap_err();
     assert!(format!("{e:?}").contains("test 123"));
@@ -48,7 +48,7 @@ fn test_anyhow_error_return() -> Result<()> {
     });
 
     let instance = Instance::new(&mut store, &module, &[hello_func.into()])?;
-    let run_func = instance.get_typed_func::<(), (), _>(&mut store, "run")?;
+    let run_func = instance.get_typed_func::<(), ()>(&mut store, "run")?;
 
     let e = run_func.call(&mut store, ()).unwrap_err();
     assert!(!e.to_string().contains("test 1234"));
@@ -86,7 +86,7 @@ fn test_trap_return_downcast() -> Result<()> {
     });
 
     let instance = Instance::new(&mut store, &module, &[hello_func.into()])?;
-    let run_func = instance.get_typed_func::<(), (), _>(&mut store, "run")?;
+    let run_func = instance.get_typed_func::<(), ()>(&mut store, "run")?;
 
     let e = run_func
         .call(&mut store, ())
@@ -121,7 +121,7 @@ fn test_trap_trace() -> Result<()> {
 
     let module = Module::new(store.engine(), wat)?;
     let instance = Instance::new(&mut store, &module, &[])?;
-    let run_func = instance.get_typed_func::<(), (), _>(&mut store, "run")?;
+    let run_func = instance.get_typed_func::<(), ()>(&mut store, "run")?;
 
     let e = run_func.call(&mut store, ()).unwrap_err();
 
@@ -196,7 +196,7 @@ fn test_trap_through_host() -> Result<()> {
         &module,
         &[host_func_a.into(), host_func_b.into()],
     )?;
-    let a = instance.get_typed_func::<(), (), _>(&mut store, "a")?;
+    let a = instance.get_typed_func::<(), ()>(&mut store, "a")?;
     let err = a.call(&mut store, ()).unwrap_err();
     let trace = err.downcast_ref::<WasmBacktrace>().unwrap().frames();
     assert_eq!(trace.len(), 3);
@@ -222,7 +222,7 @@ fn test_trap_backtrace_disabled() -> Result<()> {
 
     let module = Module::new(store.engine(), wat)?;
     let instance = Instance::new(&mut store, &module, &[])?;
-    let run_func = instance.get_typed_func::<(), (), _>(&mut store, "run")?;
+    let run_func = instance.get_typed_func::<(), ()>(&mut store, "run")?;
 
     let e = run_func.call(&mut store, ()).unwrap_err();
     assert!(e.downcast_ref::<WasmBacktrace>().is_none());
@@ -245,7 +245,7 @@ fn test_trap_trace_cb() -> Result<()> {
 
     let module = Module::new(store.engine(), wat)?;
     let instance = Instance::new(&mut store, &module, &[fn_func.into()])?;
-    let run_func = instance.get_typed_func::<(), (), _>(&mut store, "run")?;
+    let run_func = instance.get_typed_func::<(), ()>(&mut store, "run")?;
 
     let e = run_func.call(&mut store, ()).unwrap_err();
 
@@ -271,7 +271,7 @@ fn test_trap_stack_overflow() -> Result<()> {
 
     let module = Module::new(store.engine(), wat)?;
     let instance = Instance::new(&mut store, &module, &[])?;
-    let run_func = instance.get_typed_func::<(), (), _>(&mut store, "run")?;
+    let run_func = instance.get_typed_func::<(), ()>(&mut store, "run")?;
 
     let e = run_func.call(&mut store, ()).unwrap_err();
 
@@ -301,7 +301,7 @@ fn trap_display_pretty() -> Result<()> {
 
     let module = Module::new(store.engine(), wat)?;
     let instance = Instance::new(&mut store, &module, &[])?;
-    let run_func = instance.get_typed_func::<(), (), _>(&mut store, "bar")?;
+    let run_func = instance.get_typed_func::<(), ()>(&mut store, "bar")?;
 
     let e = run_func.call(&mut store, ()).unwrap_err();
     assert_eq!(
@@ -345,7 +345,7 @@ fn trap_display_multi_module() -> Result<()> {
     "#;
     let module = Module::new(store.engine(), wat)?;
     let instance = Instance::new(&mut store, &module, &[bar])?;
-    let bar2 = instance.get_typed_func::<(), (), _>(&mut store, "bar2")?;
+    let bar2 = instance.get_typed_func::<(), ()>(&mut store, "bar2")?;
 
     let e = bar2.call(&mut store, ()).unwrap_err();
     assert_eq!(
@@ -405,12 +405,12 @@ fn rust_panic_import() -> Result<()> {
     let func = Func::new(&mut store, sig, |_, _, _| panic!("this is a panic"));
     let func2 = Func::wrap(&mut store, || panic!("this is another panic"));
     let instance = Instance::new(&mut store, &module, &[func.into(), func2.into()])?;
-    let func = instance.get_typed_func::<(), (), _>(&mut store, "foo")?;
+    let func = instance.get_typed_func::<(), ()>(&mut store, "foo")?;
     let err =
         panic::catch_unwind(AssertUnwindSafe(|| drop(func.call(&mut store, ())))).unwrap_err();
     assert_eq!(err.downcast_ref::<&'static str>(), Some(&"this is a panic"));
 
-    let func = instance.get_typed_func::<(), (), _>(&mut store, "bar")?;
+    let func = instance.get_typed_func::<(), ()>(&mut store, "bar")?;
     let err = panic::catch_unwind(AssertUnwindSafe(|| {
         drop(func.call(&mut store, ()));
     }))
@@ -468,7 +468,7 @@ fn rust_catch_panic_import() -> Result<()> {
     });
 
     let instance = Instance::new(&mut store, &module, &[panic.into(), catch_panic.into()])?;
-    let run = instance.get_typed_func::<(), (), _>(&mut store, "run")?;
+    let run = instance.get_typed_func::<(), ()>(&mut store, "run")?;
     let trap = run.call(&mut store, ()).unwrap_err();
     let trace = trap.downcast_ref::<WasmBacktrace>().unwrap().frames();
     assert_eq!(trace.len(), 1);
@@ -613,7 +613,7 @@ fn present_after_module_drop() -> Result<()> {
     let mut store = Store::<()>::default();
     let module = Module::new(store.engine(), r#"(func (export "foo") unreachable)"#)?;
     let instance = Instance::new(&mut store, &module, &[])?;
-    let func = instance.get_typed_func::<(), (), _>(&mut store, "foo")?;
+    let func = instance.get_typed_func::<(), ()>(&mut store, "foo")?;
 
     println!("asserting before we drop modules");
     assert_trap(func.call(&mut store, ()).unwrap_err());
@@ -811,13 +811,13 @@ fn multithreaded_traps() -> Result<()> {
     let instance = Instance::new(&mut store, &module, &[])?;
 
     assert!(instance
-        .get_typed_func::<(), (), _>(&mut store, "run")?
+        .get_typed_func::<(), ()>(&mut store, "run")?
         .call(&mut store, ())
         .is_err());
 
     let handle = std::thread::spawn(move || {
         assert!(instance
-            .get_typed_func::<(), (), _>(&mut store, "run")
+            .get_typed_func::<(), ()>(&mut store, "run")
             .unwrap()
             .call(&mut store, ())
             .is_err());
@@ -843,7 +843,7 @@ fn traps_without_address_map() -> Result<()> {
 
     let module = Module::new(store.engine(), wat)?;
     let instance = Instance::new(&mut store, &module, &[])?;
-    let run_func = instance.get_typed_func::<(), (), _>(&mut store, "run")?;
+    let run_func = instance.get_typed_func::<(), ()>(&mut store, "run")?;
 
     let e = run_func.call(&mut store, ()).unwrap_err();
 
@@ -891,7 +891,7 @@ fn catch_trap_calling_across_stores() -> Result<()> {
             let data = ctx.data_mut();
             let func = data
                 .child_instance
-                .get_typed_func::<(), (), _>(&mut data.child_store, "trap")
+                .get_typed_func::<(), ()>(&mut data.child_store, "trap")
                 .expect("trap function should be exported");
 
             let trap = func.call(&mut data.child_store, ()).unwrap_err();
@@ -935,7 +935,7 @@ fn catch_trap_calling_across_stores() -> Result<()> {
 
     let parent_instance = linker.instantiate(&mut store, &parent_module)?;
 
-    let func = parent_instance.get_typed_func::<(), (), _>(&mut store, "run")?;
+    let func = parent_instance.get_typed_func::<(), ()>(&mut store, "run")?;
     func.call(store, ())?;
 
     Ok(())
@@ -994,7 +994,7 @@ async fn async_then_sync_trap() -> Result<()> {
 
         log::info!("Calling `c`...");
         let c = sync_instance
-            .get_typed_func::<(), (), _>(&mut *sync_store, "c")
+            .get_typed_func::<(), ()>(&mut *sync_store, "c")
             .unwrap();
         c.call(sync_store, ())?;
         Ok(())
@@ -1006,7 +1006,7 @@ async fn async_then_sync_trap() -> Result<()> {
 
     log::info!("Calling `a`...");
     let a = async_instance
-        .get_typed_func::<(), (), _>(&mut async_store, "a")
+        .get_typed_func::<(), ()>(&mut async_store, "a")
         .unwrap();
     let trap = a.call_async(&mut async_store, ()).await.unwrap_err();
 
@@ -1074,7 +1074,7 @@ async fn sync_then_async_trap() -> Result<()> {
 
         log::info!("Calling `c`...");
         let c = async_instance
-            .get_typed_func::<(), (), _>(&mut *async_store, "c")
+            .get_typed_func::<(), ()>(&mut *async_store, "c")
             .unwrap();
         tokio::task::block_in_place(|| {
             tokio::runtime::Handle::current()
@@ -1087,7 +1087,7 @@ async fn sync_then_async_trap() -> Result<()> {
 
     log::info!("Calling `a`...");
     let a = sync_instance
-        .get_typed_func::<(), (), _>(&mut sync_store, "a")
+        .get_typed_func::<(), ()>(&mut sync_store, "a")
         .unwrap();
     let trap = a.call(&mut sync_store, ()).unwrap_err();