LikeLakers2 opened issue #12332:
Hi, the title pretty much tells it all.
Basically, I was browsing the
wasmtimeandwasmtime-wasidocs, in an attempt to get an idea of how I might use this in a program.Whilst doing so, I noticed that
wasmtime-wasi::WasiCtxBuilder::build()takes&mut self, but then proceeds to say that aWasiCtxBuildercan only be built once - with any additionalbuild()s causing a panic.So my question is, why not just have
build()takeself- meaning that callingbuild()will consume theWasiCtxBuilder? That way, Rust's type system itself will prevent the reuse ofWasiCtxBuilderafter it's been built.This would align
WasiCtxBuilderwith most other builder types out there in rust crates.Should this change be desired, I'd be happy to open a PR making the change.
Alternatively, if changing it to
selfis not desired/possible, I would enjoy understanding why.
LikeLakers2 edited issue #12332:
Hi, the title pretty much tells it all.
Basically, I was browsing the
wasmtimeandwasmtime-wasidocs, in an attempt to get an idea of how I might use this in a program.Whilst doing so, I noticed that
wasmtime-wasi::WasiCtxBuilder::build()takes&mut self, but then proceeds to say that aWasiCtxBuildercan only be built once - with any additionalbuild()s causing a panic.So my question is, why not just have
build()takeself- meaning that callingbuild()will consume theWasiCtxBuilder? That way, Rust's type system itself will prevent the reuse ofWasiCtxBuilderafter it's been built.This would align
WasiCtxBuilderwith most other builder types out there in rust crates.Alternatively, if changing it to
selfis not desired/possible, I would enjoy understanding why.
bjorn3 commented on issue #12332:
All
WasiCtxBuildermethods return&mut Self. Ifbuilddidn't, it wouldn't be possible to chain it asWasiCtx::builder().stdin(...).build(). You did need to dolet mut builder = WasiCtx::builder(); builder.stdin(...); builder.build()then instead.
alexcrichton commented on issue #12332:
The decision for
&mut selfis to more ergonomically enable incremental initialization at the cost of not statically preventing use-after-build. For example you can have a function that takes&mut WasiCtxtBuilderand configures. With by-move, the main alternative, it would mean that a helper function would need to takeWasiCtxBuilderand also returnWasiCtxBuilderwhich can be quite cumbersome. Personally I find conditional initialization more cumbersome in by-move initialization as well.As @bjorn3 mentioned changing just
build()to takeselfwould also make usage less ergonomic. It would require changing all the methods to take-and-returnself.
alexcrichton added the wasmtime:api label to Issue #12332.
LikeLakers2 commented on issue #12332:
Then
WasiCtxBuildershould be changed to be reusable for multiplebuild()s. It makes no sense for there to be aWasiCtxBuilderstill lying around if we cannot reuse it.
LikeLakers2 edited a comment on issue #12332:
Then
WasiCtxBuildershould be changed to be reusable for multiplebuild()s, as it makes no sense forWasiCtxBuilderto artificially prevent multiplebuild()s.
LikeLakers2 deleted a comment on issue #12332:
Then
WasiCtxBuildershould be changed to be reusable for multiplebuild()s, as it makes no sense forWasiCtxBuilderto artificially prevent multiplebuild()s.
LikeLakers2 commented on issue #12332:
The decision for
&mut selfis to more ergonomically enable incremental initialization at the cost of not statically preventing use-after-build.But
WasiCtxBuilderartificially prevents its use afterbuild(), by way of aself.builtvariable. <https://github.com/bytecodealliance/wasmtime/blob/fb1827ee662b7b8fb7dbeb13fea8f7477e0339f9/crates/wasi/src/ctx.rs#L431-L451> It effectively makes it a one-shot builder - unreusable unless you callWasiCtxBuilder::new()again.For example you can have a function that takes
&mut WasiCtxtBuilderand configures. With by-move, the main alternative, it would mean that a helper function would need to takeWasiCtxBuilderand also returnWasiCtxBuilderwhich can be quite cumbersome.The helper function wouldn't be chainable to the rest though - plus, you can still just pass
&mut WasiCtxBuilderto the function. As an example: https://play.rust-lang.org/?version=stable&mode=debug&edition=2024&gist=974d87f544e8af20129960c2436b31e2As bjorn3 mentioned changing just
build()to takeselfwould also make usage less ergonomic. It would require changing all the methods to take-and-returnself.Then they should all take
self- or otherwise allow reuse after building.
LikeLakers2 edited a comment on issue #12332:
The decision for
&mut selfis to more ergonomically enable incremental initialization at the cost of not statically preventing use-after-build.But
WasiCtxBuilderartificially prevents its use afterbuild(), by way of aself.builtvariable. <https://github.com/bytecodealliance/wasmtime/blob/fb1827ee662b7b8fb7dbeb13fea8f7477e0339f9/crates/wasi/src/ctx.rs#L431-L451> It effectively makes it a one-shot builder - unreusable unless you callWasiCtxBuilder::new()again.For example you can have a function that takes
&mut WasiCtxtBuilderand configures. With by-move, the main alternative, it would mean that a helper function would need to takeWasiCtxBuilderand also returnWasiCtxBuilderwhich can be quite cumbersome.The helper function wouldn't be chainable to the rest in either case though - plus, you can still just pass
&mut WasiCtxBuilderto the function. As an example: https://play.rust-lang.org/?version=stable&mode=debug&edition=2024&gist=974d87f544e8af20129960c2436b31e2As bjorn3 mentioned changing just
build()to takeselfwould also make usage less ergonomic. It would require changing all the methods to take-and-returnself.Then they should all take
self- or otherwise allow reuse after building.
LikeLakers2 edited a comment on issue #12332:
The decision for
&mut selfis to more ergonomically enable incremental initialization at the cost of not statically preventing use-after-build.But
WasiCtxBuilderartificially prevents its reuse afterbuild(), by way of aself.builtvariable. <https://github.com/bytecodealliance/wasmtime/blob/fb1827ee662b7b8fb7dbeb13fea8f7477e0339f9/crates/wasi/src/ctx.rs#L431-L451> It effectively makes it a one-shot builder - unreusable unless you callWasiCtxBuilder::new()again.For example you can have a function that takes
&mut WasiCtxtBuilderand configures. With by-move, the main alternative, it would mean that a helper function would need to takeWasiCtxBuilderand also returnWasiCtxBuilderwhich can be quite cumbersome.The helper function wouldn't be chainable to the rest in either case though - plus, you can still just pass
&mut WasiCtxBuilderto the function. As an example: https://play.rust-lang.org/?version=stable&mode=debug&edition=2024&gist=974d87f544e8af20129960c2436b31e2As bjorn3 mentioned changing just
build()to takeselfwould also make usage less ergonomic. It would require changing all the methods to take-and-returnself.Then they should all take
self- or otherwise allow reuse after building.
LikeLakers2 edited a comment on issue #12332:
The decision for
&mut selfis to more ergonomically enable incremental initialization at the cost of not statically preventing use-after-build.But
WasiCtxBuilderartificially prevents its reuse afterbuild(), by way of aself.builtvariable. It effectively makes it a one-shot builder - unreusable unless you callWasiCtxBuilder::new()again - while simultaneously leaving a uselessWasiCtxBuilderlying around.For example you can have a function that takes
&mut WasiCtxtBuilderand configures. With by-move, the main alternative, it would mean that a helper function would need to takeWasiCtxBuilderand also returnWasiCtxBuilderwhich can be quite cumbersome.The helper function wouldn't be chainable to the rest in either case though - plus, you can still just pass
&mut WasiCtxBuilderto the function. As an example: https://play.rust-lang.org/?version=stable&mode=debug&edition=2024&gist=974d87f544e8af20129960c2436b31e2As bjorn3 mentioned changing just
build()to takeselfwould also make usage less ergonomic. It would require changing all the methods to take-and-returnself.Then they should all take
self- or otherwise allow reuse after building.
LikeLakers2 edited a comment on issue #12332:
The decision for
&mut selfis to more ergonomically enable incremental initialization at the cost of not statically preventing use-after-build.But
WasiCtxBuilderartificially prevents its reuse afterbuild(), by way of aself.builtvariable. It effectively makes it a one-shot builder - unreusable unless you callWasiCtxBuilder::new()again - while simultaneously leaving a uselessWasiCtxBuilderlying around.For example you can have a function that takes
&mut WasiCtxtBuilderand configures. With by-move, the main alternative, it would mean that a helper function would need to takeWasiCtxBuilderand also returnWasiCtxBuilderwhich can be quite cumbersome.I mean... such a helper function wouldn't be chainable to the rest of the builder functions.
Also, it wouldn't need to take
WasiCtxBuilder- Rust lets you create mutable references to a variable via&mut some_variable.As bjorn3 mentioned changing just
build()to takeselfwould also make usage less ergonomic. It would require changing all the methods to take-and-returnself.Then they should all take
self- or otherwise allow reuse after building.
bjorn3 commented on issue #12332:
But WasiCtxBuilder artificially prevents its reuse after build(), by way of a self.built variable.
It doesn't artificially prevent reuse.
self.builtis to prevent accidentally reusing it when doing so would be a clear bug due to.build()effectively having to reset the builder to it's default value as it needs to move it's contents into the createdWasiCtx. Some things can't be cloned (like the randomness source, clocks,socket_addr_checkclosure), while cloning others would needlessly make building slower for the common case of not reusing the builder.
LikeLakers2 commented on issue #12332:
@bjorn3 Then... why not just have everything take
selfand avoid this whole kerfuffle? If the type system prevents its reuse,WasiCtxBuilderwouldn't need to handle preventing its own reuse.
LikeLakers2 edited a comment on issue #12332:
@bjorn3 Then... why not just have everything take
selfand avoid this whole kerfuffle? Doing so means the type system prevents its reuse, andWasiCtxBuilderwouldn't need to handle preventing its own reuse.
bjorn3 commented on issue #12332:
Because it is less ergonomic when conditionally configuring the builder unfortunately.
LikeLakers2 commented on issue #12332:
...No it's not? From my experience, and from a playground linked below, you can still do chaining with
self.
LikeLakers2 edited a comment on issue #12332:
...No it's not? From my experience, and from testing in the playground linked below, you can still do chaining with
self.
LikeLakers2 deleted a comment on issue #12332:
...No it's not? From my experience, and from testing in the playground linked below, you can still do chaining with
self.
LikeLakers2 commented on issue #12332:
...I'm becoming very confused as to what you all want.
From what I understand, what we want to allow is something like this:
let mut builder = MyBuilder::new() .set_a("a") .set_b("b"); if should_set_c { builder.set_c("c"); } return builder.build();But that's simply not possible under Rust's type system, regardless of whether we use
&mut selforself.If we use
&mut self, it has to be like this (<https://play.rust-lang.org/?version=stable&mode=debug&edition=2024&gist=fbd31e733b87d5017513d4d8b33a96d0>):let mut builder = MyBuilder::default(); builder .set_a("a") .set_b("b"); if should_set_c { builder.set_c("c"); } return builder.build();But that's not considered ergonomic because it's not chaining to the original
MyBuilder::default().Meanwhile, if we use
self, it has to be like this (<https://play.rust-lang.org/?version=stable&mode=debug&edition=2024&gist=12fa26b158b08905cfb8b85b23268a98>):let mut builder = MyBuilder::default() .set_a("a") .set_b("b"); if should_set_c { builder = builder.set_c("c"); } return builder.build();But that's not considered ergonomic because of the
builder =inside theifstatement.So what is it the team wants?
LikeLakers2 edited a comment on issue #12332:
...I'm becoming very confused as to what you all want.
From what I understand, what we want to allow is something like this:
let mut builder = MyBuilder::new() .set_a("a") .set_b("b"); if should_set_c { builder.set_c("c"); } return builder.build();But that's simply not possible under Rust's type system, regardless of whether we use
&mut selforself.If we use
&mut selfand return&mut Self, it has to be like this (<https://play.rust-lang.org/?version=stable&mode=debug&edition=2024&gist=fbd31e733b87d5017513d4d8b33a96d0>):let mut builder = MyBuilder::default(); builder .set_a("a") .set_b("b"); if should_set_c { builder.set_c("c"); } return builder.build();But that's not considered ergonomic because it's not chaining to the original
MyBuilder::default().Meanwhile, if we use
selfand returnSelf, it has to be like this (<https://play.rust-lang.org/?version=stable&mode=debug&edition=2024&gist=12fa26b158b08905cfb8b85b23268a98>):let mut builder = MyBuilder::default() .set_a("a") .set_b("b"); if should_set_c { builder = builder.set_c("c"); } return builder.build();But that's not considered ergonomic because of the
builder =inside theifstatement.So what is it the team wants?
LikeLakers2 edited a comment on issue #12332:
...I'm becoming very confused as to what you all want.
From what I understand, what we want to allow is something like this:
let mut builder = MyBuilder::default() .set_a("a") .set_b("b"); if should_set_c { builder.set_c("c"); } return builder.build();But that's simply not possible under Rust's type system, regardless of whether we use
&mut selforself.If we use
&mut selfand return&mut Self, it has to be like this (<https://play.rust-lang.org/?version=stable&mode=debug&edition=2024&gist=fbd31e733b87d5017513d4d8b33a96d0>):let mut builder = MyBuilder::default(); builder .set_a("a") .set_b("b"); if should_set_c { builder.set_c("c"); } return builder.build();But that's not considered ergonomic because it's not chaining to the original
MyBuilder::default().Meanwhile, if we use
selfand returnSelf, it has to be like this (<https://play.rust-lang.org/?version=stable&mode=debug&edition=2024&gist=12fa26b158b08905cfb8b85b23268a98>):let mut builder = MyBuilder::default() .set_a("a") .set_b("b"); if should_set_c { builder = builder.set_c("c"); } return builder.build();But that's not considered ergonomic because of the
builder =inside theifstatement.So what is it the team wants?
cfallin commented on issue #12332:
@LikeLakers2 whether you mean it or not, you're coming across as somewhat confrontational. I'd encourage you to take a step back and determine what it is that you want: are you trying to understand our design decisions? Are you pushing for an alternative design? Do you see a contradiction that should be addressed?
The current design is, IMHO, a nice compromise. I believe you've gotten answers above regarding why we chose not to take alternatives (pure
self: disallows control flow;selftobuild: disallows fully chained approach withbuildinline). You seem to be pointing to a "gotcha" contradiction, but I don't see the issue with your first listing. Is there some other issue you'd like to raise?
LikeLakers2 commented on issue #12332:
@cfallin I'm sorry if I come across as confrontational, but I'm more confused and frustrated trying to figure out what it is the team wants.
From my POV, the team seem to want things that even the current design doesn't provide - all the while saying that the alternatives can't provide what they want, despite the alternatives providing exactly those.
So I'm left wondering how the current design is better, and proposed designs are worse - even ignoring my own opinion, it sounds like the team thinks both designs suck?
LikeLakers2 edited a comment on issue #12332:
@cfallin I'm sorry if I come across as confrontational, but I'm more confused and frustrated trying to figure out what it is the team wants.
From my POV, the team seem to want things that even the current design doesn't provide - all the while saying that the alternatives can't provide what they want, despite the alternatives providing exactly those.
So I'm left wondering how the current design is better, and proposed designs are worse - even ignoring my own opinion, it sounds like the team thinks both designs suck? Which leads me to be frustrated and confused as to what is even desired here - and thus, my frustration with this issue.
cfallin commented on issue #12332:
No, I don't think the current design sucks. I think it's fine and meets our needs. I believe that we've answered above why the
buildmethod takes&mut self, which was your original question, and why suggested alternatives do not work, so I'll go ahead and close this issue. If you have concrete suggestions for other APIs that meet all of the stated use cases above and are improvements, though, please feel free to file a issue or PR with that suggestion. Thanks!
cfallin closed issue #12332:
Hi, the title pretty much tells it all.
Basically, I was browsing the
wasmtimeandwasmtime-wasidocs, in an attempt to get an idea of how I might use this in a program.Whilst doing so, I noticed that
wasmtime-wasi::WasiCtxBuilder::build()takes&mut self, but then proceeds to say that aWasiCtxBuildercan only be built once - with any additionalbuild()s causing a panic.So my question is, why not just have
build()takeself- meaning that callingbuild()will consume theWasiCtxBuilder? That way, Rust's type system itself will prevent the reuse ofWasiCtxBuilderafter it's been built.This would align
WasiCtxBuilderwith most other builder types out there in rust crates.Alternatively, if changing it to
selfis not desired/possible, I would enjoy understanding why.
LikeLakers2 commented on issue #12332:
@cfallin How do I explain, in a non-confrontational manner, that I am extremely confused as to what's even desired here? How do I explain that, even if an answer was given, I'm not understanding the answer?
Please, I'm trying desperately to not get angry here - but it's extremely hard when I try to explain my thoughts, that I don't understand the answers I've been given, only for them to be ignored in favor of "I believe we've already answered you."
LikeLakers2 edited a comment on issue #12332:
@cfallin How do I explain, in a non-confrontational manner, that I am extremely confused as to what's even desired here? How do I explain that, even if an answer was given, I'm not understanding the answer?
Please, I'm trying desperately to not get frustrated here - but it's extremely hard when I try to explain my thoughts, that I don't understand the answers I've been given, only for them to be ignored in favor of "I believe we've already answered you."
LikeLakers2 deleted a comment on issue #12332:
@cfallin How do I explain, in a non-confrontational manner, that I am extremely confused as to what's even desired here? How do I explain that, even if an answer was given, I'm not understanding the answer?
Please, I'm trying desperately to not get frustrated here - but it's extremely hard when I try to explain my thoughts, that I don't understand the answers I've been given, only for them to be ignored in favor of "I believe we've already answered you."
LikeLakers2 commented on issue #12332:
@cfallin, With all due respect: I'm trying to understand the stated use cases, and failing.
I'm trying my best to understand what's desired, and what's not - but I genuinely cannot feel I've gotten any answers out of this ordeal. Or if I have, it seems like they're all conflicting with one another, or with how Rust's type system works.
To be clear: I cannot give any concrete suggestions if I do not understand what is even desired.
So please help me understand what is desired? I'm genuinely trying, but the answers above have left me even more confused than when I'm started.
LikeLakers2 edited a comment on issue #12332:
@cfallin, With all due respect: I'm trying to understand the stated use cases, but failing to do so. It's leaving me frustrated.
I'm trying my best to understand what's desired, and what's not - but I genuinely cannot feel I've gotten any answers out of this ordeal. Or if I have, it seems like they're all conflicting with one another, or with how Rust's type system works.
To be clear: I cannot give any concrete suggestions if I do not understand what is even desired.
So please help me understand what is desired? I'm genuinely trying, but the answers above have left me even more confused than when I'm started.
LikeLakers2 edited a comment on issue #12332:
@cfallin, I'm trying my best to understand what's desired, and what's not - but I genuinely cannot feel I've gotten any answers out of this ordeal. Or if I have, it seems like they're all conflicting with one another, or with how Rust's type system works.
To be clear: I cannot give any concrete suggestions if I do not understand what is even desired.
So please help me understand what is desired? I'm genuinely trying, but the answers above have left me even more confused than when I'm started.
LikeLakers2 edited a comment on issue #12332:
@cfallin, I'm trying my best to understand what's desired, and what's not - but I genuinely cannot feel I've gotten any answers out of this ordeal. Or if I have, it seems like they're all conflicting with one another, or with how Rust's type system works.
To be clear: I cannot give any concrete suggestions if I do not understand what is even desired.
So please help me understand what is desired? I'm genuinely trying, but the answers above have left me even more confused than when I started.
cfallin commented on issue #12332:
Sure. To summarize the above, there are a few things we want in a builder API:
- We want chaining to work.
builder.a(...).b(...).c(...).We want conditional updates to be reasonably ergonomic:
let mut builder = Builder::new(); builder.a(...).b(...).c(...); if cond { builder.d(...).e(...); }We want a one-liner to work for simple cases:
Builder::new().a(..).build().I believe that the second case above means we can't take
selfon setter (re-assigning the binding conditionally is not ergonomic enough) and the third case above means we can't takeselfonbuildif setters take&mut self.Does that answer your questions?
LikeLakers2 commented on issue #12332:
That does, but that leaves me with another question:
As it stands,
WasiCtxBuilderresets its state onbuild()by callingSelf::new():This should leave it in a valid state to be reused - except that it artificially prevents reuse by way of the
self.builtvariable being set totrue.So is there any reason we can't remove the
self.builtvariable, and allow it to be reused?
LikeLakers2 edited a comment on issue #12332:
That does, but that leaves me with another question:
As it stands,
WasiCtxBuilderresets its state onbuild()by replacing itself withSelf::new():This should leave it in a valid state to be reused - except that it artificially prevents reuse by way of the
self.builtvariable being set totrue.So is there any reason we can't remove the
self.builtvariable, and allow it to be reused?
LikeLakers2 edited a comment on issue #12332:
That does, but that leaves me with another question:
As it stands,
WasiCtxBuilderresets its state onbuild()by replacing itself withSelf::new():This should leave it in a valid state to be reused - except that it prevents reuse by way of the
self.builtvariable being set totrue.So is there any reason we can't remove the
self.builtvariable, and allow it to be reused?
cfallin commented on issue #12332:
I believe bjorn3 answered that above:
self.built is to prevent accidentally reusing it when doing so would be a clear bug due to .build() effectively having to reset the builder to it's default value as it needs to move it's contents into the created WasiCtx. Some things can't be cloned (like the randomness source, clocks, socket_addr_check closure), while cloning others would needlessly make building slower for the common case of not reusing the builder.
LikeLakers2 commented on issue #12332:
Okay, I see where I went wrong on the first read of that. The blocking of reuse is to ensure that users don't accidentally use an unconfigured
WasiCtxBuilder.But now I'm wondering: Why not document that the
WasiCtxBuilderwill be returned to its default state onbuild(), and allow reuse?I suggest this because, under the current API, if multiple
WasiCtxs are needed, then each call toWasiCtxBuilder::build()then toWasiCtxBuilder::new()will be effectively two calls tonew(), rather than one.Allowing reuse here would reduce the number of times expensive operations (such as allocating heap space for
Vecs) happen.
LikeLakers2 edited a comment on issue #12332:
Okay, I see where I went wrong on the first read of that. The blocking of reuse is to ensure that users don't accidentally use an unconfigured
WasiCtxBuilder.But now I'm wondering: Why not document that the
WasiCtxBuilderwill be returned to its default state onbuild(), and allow reuse?I suggest this because, under the current API, if multiple
WasiCtxs are needed, then each call toWasiCtxBuilder::build()then toWasiCtxBuilder::new()will be effectively two calls tonew(), rather than one.Allowing reuse here would reduce the number of times expensive operations (such as allocating heap space for
Vecs) happen.If there's a concern that people may not read the documentation, then we could perhaps allow such reuse through a function called
build_and_reset()- making it explicit, even without the docs visible, that theWasiCtxBuilderwill be reset to a default state.
Last updated: Jan 29 2026 at 13:25 UTC