Stream: git-wasmtime

Topic: wasmtime / issue #12332 Why does `wasmtime-wasi::WasiCtxB...


view this post on Zulip Wasmtime GitHub notifications bot (Jan 13 2026 at 11:16):

LikeLakers2 opened issue #12332:

Hi, the title pretty much tells it all.

Basically, I was browsing the wasmtime and wasmtime-wasi docs, 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 a WasiCtxBuilder can only be built once - with any additional build()s causing a panic.

So my question is, why not just have build() take self - meaning that calling build() will consume the WasiCtxBuilder? That way, Rust's type system itself will prevent the reuse of WasiCtxBuilder after it's been built.

This would align WasiCtxBuilder with 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 self is not desired/possible, I would enjoy understanding why.

view this post on Zulip Wasmtime GitHub notifications bot (Jan 13 2026 at 11:24):

LikeLakers2 edited issue #12332:

Hi, the title pretty much tells it all.

Basically, I was browsing the wasmtime and wasmtime-wasi docs, 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 a WasiCtxBuilder can only be built once - with any additional build()s causing a panic.

So my question is, why not just have build() take self - meaning that calling build() will consume the WasiCtxBuilder? That way, Rust's type system itself will prevent the reuse of WasiCtxBuilder after it's been built.

This would align WasiCtxBuilder with most other builder types out there in rust crates.

Alternatively, if changing it to self is not desired/possible, I would enjoy understanding why.

view this post on Zulip Wasmtime GitHub notifications bot (Jan 13 2026 at 12:26):

bjorn3 commented on issue #12332:

All WasiCtxBuilder methods return &mut Self . If build didn't, it wouldn't be possible to chain it as WasiCtx::builder().stdin(...).build(). You did need to do let mut builder = WasiCtx::builder(); builder.stdin(...); builder.build() then instead.

view this post on Zulip Wasmtime GitHub notifications bot (Jan 13 2026 at 15:05):

alexcrichton commented on issue #12332:

The decision for &mut self is 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 WasiCtxtBuilder and configures. With by-move, the main alternative, it would mean that a helper function would need to take WasiCtxBuilder and also return WasiCtxBuilder which can be quite cumbersome. Personally I find conditional initialization more cumbersome in by-move initialization as well.

As @bjorn3 mentioned changing just build() to take self would also make usage less ergonomic. It would require changing all the methods to take-and-return self.

view this post on Zulip Wasmtime GitHub notifications bot (Jan 13 2026 at 15:05):

alexcrichton added the wasmtime:api label to Issue #12332.

view this post on Zulip Wasmtime GitHub notifications bot (Jan 13 2026 at 19:11):

LikeLakers2 commented on issue #12332:

Then WasiCtxBuilder should be changed to be reusable for multiple build()s. It makes no sense for there to be a WasiCtxBuilder still lying around if we cannot reuse it.

view this post on Zulip Wasmtime GitHub notifications bot (Jan 13 2026 at 19:13):

LikeLakers2 edited a comment on issue #12332:

Then WasiCtxBuilder should be changed to be reusable for multiple build()s, as it makes no sense for WasiCtxBuilder to artificially prevent multiple build()s.

view this post on Zulip Wasmtime GitHub notifications bot (Jan 13 2026 at 19:14):

LikeLakers2 deleted a comment on issue #12332:

Then WasiCtxBuilder should be changed to be reusable for multiple build()s, as it makes no sense for WasiCtxBuilder to artificially prevent multiple build()s.

view this post on Zulip Wasmtime GitHub notifications bot (Jan 13 2026 at 19:29):

LikeLakers2 commented on issue #12332:

The decision for &mut self is to more ergonomically enable incremental initialization at the cost of not statically preventing use-after-build.

But WasiCtxBuilder artificially prevents its use after build(), by way of a self.built variable. <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 call WasiCtxBuilder::new() again.

For example you can have a function that takes &mut WasiCtxtBuilder and configures. With by-move, the main alternative, it would mean that a helper function would need to take WasiCtxBuilder and also return WasiCtxBuilder which can be quite cumbersome.

The helper function wouldn't be chainable to the rest though - plus, you can still just pass &mut WasiCtxBuilder to the function. As an example: https://play.rust-lang.org/?version=stable&mode=debug&edition=2024&gist=974d87f544e8af20129960c2436b31e2

As bjorn3 mentioned changing just build() to take self would also make usage less ergonomic. It would require changing all the methods to take-and-return self.

Then they should all take self - or otherwise allow reuse after building.

view this post on Zulip Wasmtime GitHub notifications bot (Jan 13 2026 at 19:30):

LikeLakers2 edited a comment on issue #12332:

The decision for &mut self is to more ergonomically enable incremental initialization at the cost of not statically preventing use-after-build.

But WasiCtxBuilder artificially prevents its use after build(), by way of a self.built variable. <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 call WasiCtxBuilder::new() again.

For example you can have a function that takes &mut WasiCtxtBuilder and configures. With by-move, the main alternative, it would mean that a helper function would need to take WasiCtxBuilder and also return WasiCtxBuilder which 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 WasiCtxBuilder to the function. As an example: https://play.rust-lang.org/?version=stable&mode=debug&edition=2024&gist=974d87f544e8af20129960c2436b31e2

As bjorn3 mentioned changing just build() to take self would also make usage less ergonomic. It would require changing all the methods to take-and-return self.

Then they should all take self - or otherwise allow reuse after building.

view this post on Zulip Wasmtime GitHub notifications bot (Jan 13 2026 at 19:32):

LikeLakers2 edited a comment on issue #12332:

The decision for &mut self is to more ergonomically enable incremental initialization at the cost of not statically preventing use-after-build.

But WasiCtxBuilder artificially prevents its reuse after build(), by way of a self.built variable. <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 call WasiCtxBuilder::new() again.

For example you can have a function that takes &mut WasiCtxtBuilder and configures. With by-move, the main alternative, it would mean that a helper function would need to take WasiCtxBuilder and also return WasiCtxBuilder which 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 WasiCtxBuilder to the function. As an example: https://play.rust-lang.org/?version=stable&mode=debug&edition=2024&gist=974d87f544e8af20129960c2436b31e2

As bjorn3 mentioned changing just build() to take self would also make usage less ergonomic. It would require changing all the methods to take-and-return self.

Then they should all take self - or otherwise allow reuse after building.

view this post on Zulip Wasmtime GitHub notifications bot (Jan 13 2026 at 19:34):

LikeLakers2 edited a comment on issue #12332:

The decision for &mut self is to more ergonomically enable incremental initialization at the cost of not statically preventing use-after-build.

But WasiCtxBuilder artificially prevents its reuse after build(), by way of a self.built variable. It effectively makes it a one-shot builder - unreusable unless you call WasiCtxBuilder::new() again - while simultaneously leaving a useless WasiCtxBuilder lying around.

<https://github.com/bytecodealliance/wasmtime/blob/fb1827ee662b7b8fb7dbeb13fea8f7477e0339f9/crates/wasi/src/ctx.rs#L431-L451>

For example you can have a function that takes &mut WasiCtxtBuilder and configures. With by-move, the main alternative, it would mean that a helper function would need to take WasiCtxBuilder and also return WasiCtxBuilder which 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 WasiCtxBuilder to the function. As an example: https://play.rust-lang.org/?version=stable&mode=debug&edition=2024&gist=974d87f544e8af20129960c2436b31e2

As bjorn3 mentioned changing just build() to take self would also make usage less ergonomic. It would require changing all the methods to take-and-return self.

Then they should all take self - or otherwise allow reuse after building.

view this post on Zulip Wasmtime GitHub notifications bot (Jan 13 2026 at 19:39):

LikeLakers2 edited a comment on issue #12332:

The decision for &mut self is to more ergonomically enable incremental initialization at the cost of not statically preventing use-after-build.

But WasiCtxBuilder artificially prevents its reuse after build(), by way of a self.built variable. It effectively makes it a one-shot builder - unreusable unless you call WasiCtxBuilder::new() again - while simultaneously leaving a useless WasiCtxBuilder lying around.

<https://github.com/bytecodealliance/wasmtime/blob/fb1827ee662b7b8fb7dbeb13fea8f7477e0339f9/crates/wasi/src/ctx.rs#L431-L451>

For example you can have a function that takes &mut WasiCtxtBuilder and configures. With by-move, the main alternative, it would mean that a helper function would need to take WasiCtxBuilder and also return WasiCtxBuilder which 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.

See https://play.rust-lang.org/?version=stable&mode=debug&edition=2024&gist=2874a7a39eadc83689d3813dff582b49

As bjorn3 mentioned changing just build() to take self would also make usage less ergonomic. It would require changing all the methods to take-and-return self.

Then they should all take self - or otherwise allow reuse after building.

view this post on Zulip Wasmtime GitHub notifications bot (Jan 13 2026 at 19:39):

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.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.

view this post on Zulip Wasmtime GitHub notifications bot (Jan 13 2026 at 19:41):

LikeLakers2 commented on issue #12332:

@bjorn3 Then... why not just have everything take self and avoid this whole kerfuffle? If the type system prevents its reuse, WasiCtxBuilder wouldn't need to handle preventing its own reuse.

view this post on Zulip Wasmtime GitHub notifications bot (Jan 13 2026 at 19:41):

LikeLakers2 edited a comment on issue #12332:

@bjorn3 Then... why not just have everything take self and avoid this whole kerfuffle? Doing so means the type system prevents its reuse, and WasiCtxBuilder wouldn't need to handle preventing its own reuse.

view this post on Zulip Wasmtime GitHub notifications bot (Jan 13 2026 at 19:51):

bjorn3 commented on issue #12332:

Because it is less ergonomic when conditionally configuring the builder unfortunately.

view this post on Zulip Wasmtime GitHub notifications bot (Jan 13 2026 at 19:52):

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.

https://play.rust-lang.org/?version=stable&mode=debug&edition=2024&gist=f5cd354e0c8336dfce607e0d9a18ceb5

view this post on Zulip Wasmtime GitHub notifications bot (Jan 13 2026 at 19:54):

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.

https://play.rust-lang.org/?version=stable&mode=debug&edition=2024&gist=f5cd354e0c8336dfce607e0d9a18ceb5

view this post on Zulip Wasmtime GitHub notifications bot (Jan 13 2026 at 19:55):

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.

https://play.rust-lang.org/?version=stable&mode=debug&edition=2024&gist=f5cd354e0c8336dfce607e0d9a18ceb5

view this post on Zulip Wasmtime GitHub notifications bot (Jan 13 2026 at 20:12):

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 self or self.

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 the if statement.

So what is it the team wants?

view this post on Zulip Wasmtime GitHub notifications bot (Jan 13 2026 at 20:14):

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 self or self.

If we use &mut self and 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 self and return 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 the if statement.

So what is it the team wants?

view this post on Zulip Wasmtime GitHub notifications bot (Jan 13 2026 at 20:15):

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 self or self.

If we use &mut self and 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 self and return 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 the if statement.

So what is it the team wants?

view this post on Zulip Wasmtime GitHub notifications bot (Jan 13 2026 at 20:23):

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; self to build: disallows fully chained approach with build inline). 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?

view this post on Zulip Wasmtime GitHub notifications bot (Jan 13 2026 at 20:36):

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?

view this post on Zulip Wasmtime GitHub notifications bot (Jan 13 2026 at 20:39):

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.

view this post on Zulip Wasmtime GitHub notifications bot (Jan 13 2026 at 20:56):

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 build method 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!

view this post on Zulip Wasmtime GitHub notifications bot (Jan 13 2026 at 20:56):

cfallin closed issue #12332:

Hi, the title pretty much tells it all.

Basically, I was browsing the wasmtime and wasmtime-wasi docs, 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 a WasiCtxBuilder can only be built once - with any additional build()s causing a panic.

So my question is, why not just have build() take self - meaning that calling build() will consume the WasiCtxBuilder? That way, Rust's type system itself will prevent the reuse of WasiCtxBuilder after it's been built.

This would align WasiCtxBuilder with most other builder types out there in rust crates.

Alternatively, if changing it to self is not desired/possible, I would enjoy understanding why.

view this post on Zulip Wasmtime GitHub notifications bot (Jan 13 2026 at 21:08):

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."

view this post on Zulip Wasmtime GitHub notifications bot (Jan 13 2026 at 21:10):

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."

view this post on Zulip Wasmtime GitHub notifications bot (Jan 13 2026 at 21:12):

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."

view this post on Zulip Wasmtime GitHub notifications bot (Jan 13 2026 at 21:18):

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.

view this post on Zulip Wasmtime GitHub notifications bot (Jan 13 2026 at 21:19):

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.

view this post on Zulip Wasmtime GitHub notifications bot (Jan 13 2026 at 21:19):

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.

view this post on Zulip Wasmtime GitHub notifications bot (Jan 13 2026 at 21:20):

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.

view this post on Zulip Wasmtime GitHub notifications bot (Jan 13 2026 at 21:22):

cfallin commented on issue #12332:

Sure. To summarize the above, there are a few things we want in a builder API:

I believe that the second case above means we can't take self on setter (re-assigning the binding conditionally is not ergonomic enough) and the third case above means we can't take self on build if setters take &mut self.

Does that answer your questions?

view this post on Zulip Wasmtime GitHub notifications bot (Jan 13 2026 at 21:25):

LikeLakers2 commented on issue #12332:

That does, but that leaves me with another question:

As it stands, WasiCtxBuilder resets its state on build() by calling Self::new():

https://github.com/bytecodealliance/wasmtime/blob/fb1827ee662b7b8fb7dbeb13fea8f7477e0339f9/crates/wasi/src/ctx.rs#L431-L451

This should leave it in a valid state to be reused - except that it artificially prevents reuse by way of the self.built variable being set to true.

So is there any reason we can't remove the self.built variable, and allow it to be reused?

view this post on Zulip Wasmtime GitHub notifications bot (Jan 13 2026 at 21:25):

LikeLakers2 edited a comment on issue #12332:

That does, but that leaves me with another question:

As it stands, WasiCtxBuilder resets its state on build() by replacing itself with Self::new():

https://github.com/bytecodealliance/wasmtime/blob/fb1827ee662b7b8fb7dbeb13fea8f7477e0339f9/crates/wasi/src/ctx.rs#L431-L451

This should leave it in a valid state to be reused - except that it artificially prevents reuse by way of the self.built variable being set to true.

So is there any reason we can't remove the self.built variable, and allow it to be reused?

view this post on Zulip Wasmtime GitHub notifications bot (Jan 13 2026 at 21:26):

LikeLakers2 edited a comment on issue #12332:

That does, but that leaves me with another question:

As it stands, WasiCtxBuilder resets its state on build() by replacing itself with Self::new():

https://github.com/bytecodealliance/wasmtime/blob/fb1827ee662b7b8fb7dbeb13fea8f7477e0339f9/crates/wasi/src/ctx.rs#L431-L451

This should leave it in a valid state to be reused - except that it prevents reuse by way of the self.built variable being set to true.

So is there any reason we can't remove the self.built variable, and allow it to be reused?

view this post on Zulip Wasmtime GitHub notifications bot (Jan 13 2026 at 21:30):

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.

view this post on Zulip Wasmtime GitHub notifications bot (Jan 13 2026 at 21:48):

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 WasiCtxBuilder will be returned to its default state on build(), and allow reuse?

I suggest this because, under the current API, if multiple WasiCtxs are needed, then each call to WasiCtxBuilder::build() then to WasiCtxBuilder::new() will be effectively two calls to new(), rather than one.

Allowing reuse here would reduce the number of times expensive operations (such as allocating heap space for Vecs) happen.

view this post on Zulip Wasmtime GitHub notifications bot (Jan 13 2026 at 21:52):

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 WasiCtxBuilder will be returned to its default state on build(), and allow reuse?

I suggest this because, under the current API, if multiple WasiCtxs are needed, then each call to WasiCtxBuilder::build() then to WasiCtxBuilder::new() will be effectively two calls to new(), 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 the WasiCtxBuilder will be reset to a default state.


Last updated: Jan 29 2026 at 13:25 UTC