alexcrichton opened PR #2719 from typed-func
to main
:
This commit reimplements the
Func
API with respect to statically typed
dispatch. PreviouslyFunc
had agetN
andgetN_async
family of
methods which were implemented for 0 to 16 parameters. The return value
of these functions was animpl Fn(..)
closure with the appropriate
parameters and return values.There are a number of downsides with this approach that have become
apparent over time:
The addition of
*_async
doubled the API surface area (which is quite
large here due to one-method-per-number-of-parameters).The [documentation of
Func
][old-docs] are quite verbose and feel
"polluted" with all these getters, making it harder to understand the
other methods that can be used to interact with aFunc
.These methods unconditionally pay the cost of returning an owned
impl Fn
with a'static
lifetime. While cheap, this is still paying the
cost for cloning theStore
effectively and moving data into the
closed-over environment.Storage of the return value into a struct, for example, always
requiresBox
-ing the returned closure since it otherwise cannot be
named.Recently I had the desire to implement an "unchecked" path for
invoking wasm where you unsafely assert the type signature of a wasm
function. Doing this with today's scheme would require doubling
(again) the API surface area for both async and synchronous calls,
further polluting the documentation.The main benefit of the previous scheme is that by returning a
impl Fn
it was quite easy and ergonomic to actually invoke the function. In
practice, though, examples would often have something akin to
.get0::<()>()?()?
which is a lot of things to interpret all at once.
Note thatget0
means "0 parameters" yet a type parameter is passed.
There's also a double function invocation which looks like a lot of
characters all lined up in a row.Overall, I think that the previous design is starting to show too many
cracks and deserves a rewrite. This commit is that rewrite.The new design in this commit is to delete the
getN{,_async}
family of
functions and instead have a new API:impl Func { fn typed<P, R>(&self) -> Result<&Typed<P, R>>; } impl Typed<P, R> { fn call(&self, params: P) -> Result<R, Trap>; async fn call_async(&self, params: P) -> Result<R, Trap>; }
This should entirely replace the current scheme, albeit by slightly
losing ergonomics use cases. The idea behind the API is that the
existence ofTyped<P, R>
is a "proof" that the underlying function
takesP
and returnsR
. TheFunc::typed
method peforms a runtime
type-check to ensure that types all match up, and if successful you get
aTyped
value. Otherwise an error is returned.Once you have a
Typed
then, likeFunc
, you can eithercall
or
call_async
. The difference with aTyped
, however, is that the
params/results are statically known and hence these calls can be much
more efficient.This is a much smaller API surface area from before and should greatly
simplify theFunc
documentation. There's still a problem where
Func::wrapN_async
produces a lot of functions to document, but that's
now the sole offender. It's a nice benefit that the
statically-typed-async verisons are now expressed with anasync
function rather than a function-returning-a-future which makes it both
more efficient and easier to understand.The type
P
andR
are intended to either be bare types (e.g.i32
)
or tuples of any length (including 0). At this timeR
is only allowed
to be()
or a barei32
-style type because multi-value is not
supported with a native ABI (yet). TheP
, however, can be any size of
tuples of parameters. This is also where some ergonomics are lost
because instead off(1, 2)
you now have to writef.call((1, 2))
(note the double-parens). Similarlyf()
becomesf.call(())
.Overall I feel that this is a better tradeoff than before. While not
universally better due to the loss in ergonomics I feel that this design
is much more flexible in terms of what you can do with the return value
and also understanding the API surface area (just less to take in).[old-docs]: https://docs.rs/wasmtime/0.24.0/wasmtime/struct.Func.html#method.get0
<!--
Please ensure that the following steps are all taken care of before submitting
the PR.
[ ] This has been discussed in issue #..., or if not, please tell us why
here.[ ] A short description of what this does, why it is needed; if the
description becomes long, the matter should probably be discussed in an issue
first.[ ] This PR contains test cases, if meaningful.
- [ ] A reviewer from the core maintainer team has been assigned for this PR.
If you don't know who could review this, please indicate so. The list of
suggested reviewers on the right can help you.Please ensure all communication adheres to the code of conduct.
-->
alexcrichton updated PR #2719 from typed-func
to main
.
alexcrichton edited PR #2719 from typed-func
to main
:
This commit reimplements the
Func
API with respect to statically typed
dispatch. PreviouslyFunc
had agetN
andgetN_async
family of
methods which were implemented for 0 to 16 parameters. The return value
of these functions was animpl Fn(..)
closure with the appropriate
parameters and return values.There are a number of downsides with this approach that have become
apparent over time:
The addition of
*_async
doubled the API surface area (which is quite
large here due to one-method-per-number-of-parameters).The [documentation of
Func
][old-docs] are quite verbose and feel
"polluted" with all these getters, making it harder to understand the
other methods that can be used to interact with aFunc
.These methods unconditionally pay the cost of returning an owned
impl Fn
with a'static
lifetime. While cheap, this is still paying the
cost for cloning theStore
effectively and moving data into the
closed-over environment.Storage of the return value into a struct, for example, always
requiresBox
-ing the returned closure since it otherwise cannot be
named.Recently I had the desire to implement an "unchecked" path for
invoking wasm where you unsafely assert the type signature of a wasm
function. Doing this with today's scheme would require doubling
(again) the API surface area for both async and synchronous calls,
further polluting the documentation.The main benefit of the previous scheme is that by returning a
impl Fn
it was quite easy and ergonomic to actually invoke the function. In
practice, though, examples would often have something akin to
.get0::<()>()?()?
which is a lot of things to interpret all at once.
Note thatget0
means "0 parameters" yet a type parameter is passed.
There's also a double function invocation which looks like a lot of
characters all lined up in a row.Overall, I think that the previous design is starting to show too many
cracks and deserves a rewrite. This commit is that rewrite.The new design in this commit is to delete the
getN{,_async}
family of
functions and instead have a new API:impl Func { fn typed<P, R>(&self) -> Result<&TypedFunc<P, R>>; } impl TypedFunc<P, R> { fn call(&self, params: P) -> Result<R, Trap>; async fn call_async(&self, params: P) -> Result<R, Trap>; }
This should entirely replace the current scheme, albeit by slightly
losing ergonomics use cases. The idea behind the API is that the
existence ofTypedFunc<P, R>
is a "proof" that the underlying function
takesP
and returnsR
. TheFunc::typed
method peforms a runtime
type-check to ensure that types all match up, and if successful you get
aTypedFunc
value. Otherwise an error is returned.Once you have a
TypedFunc
then, likeFunc
, you can eithercall
or
call_async
. The difference with aTyped
, however, is that the
params/results are statically known and hence these calls can be much
more efficient.This is a much smaller API surface area from before and should greatly
simplify theFunc
documentation. There's still a problem where
Func::wrapN_async
produces a lot of functions to document, but that's
now the sole offender. It's a nice benefit that the
statically-typed-async verisons are now expressed with anasync
function rather than a function-returning-a-future which makes it both
more efficient and easier to understand.The type
P
andR
are intended to either be bare types (e.g.i32
)
or tuples of any length (including 0). At this timeR
is only allowed
to be()
or a barei32
-style type because multi-value is not
supported with a native ABI (yet). TheP
, however, can be any size of
tuples of parameters. This is also where some ergonomics are lost
because instead off(1, 2)
you now have to writef.call((1, 2))
(note the double-parens). Similarlyf()
becomesf.call(())
.Overall I feel that this is a better tradeoff than before. While not
universally better due to the loss in ergonomics I feel that this design
is much more flexible in terms of what you can do with the return value
and also understanding the API surface area (just less to take in).[old-docs]: https://docs.rs/wasmtime/0.24.0/wasmtime/struct.Func.html#method.get0
<!--
Please ensure that the following steps are all taken care of before submitting
the PR.
[ ] This has been discussed in issue #..., or if not, please tell us why
here.[ ] A short description of what this does, why it is needed; if the
description becomes long, the matter should probably be discussed in an issue
first.[ ] This PR contains test cases, if meaningful.
- [ ] A reviewer from the core maintainer team has been assigned for this PR.
If you don't know who could review this, please indicate so. The list of
suggested reviewers on the right can help you.Please ensure all communication adheres to the code of conduct.
-->
alexcrichton updated PR #2719 from typed-func
to main
.
alexcrichton updated PR #2719 from typed-func
to main
.
alexcrichton updated PR #2719 from typed-func
to main
.
alexcrichton updated PR #2719 from typed-func
to main
.
peterhuene submitted PR Review.
peterhuene created PR Review Comment:
Given this seems to be common (
get_func
followed bytyped
which then needs to be cloned), perhaps we should haveInstance::get_typed_func
to shorthand this?
peterhuene submitted PR Review.
peterhuene created PR Review Comment:
/// used instead of [`Func::call`] (which is more general, hence its
alexcrichton submitted PR Review.
alexcrichton created PR Review Comment:
Seems reasonable to me!
alexcrichton updated PR #2719 from typed-func
to main
.
alexcrichton updated PR #2719 from typed-func
to main
.
alexcrichton updated PR #2719 from typed-func
to main
.
alexcrichton updated PR #2719 from typed-func
to main
.
alexcrichton requested peterhuene for a review on PR #2719.
peterhuene submitted PR Review.
alexcrichton updated PR #2719 from typed-func
to main
.
alexcrichton merged PR #2719.
Last updated: Dec 23 2024 at 12:05 UTC