Running Zig with WASI on Cloudflare Workers(blog.cloudflare.com)
blog.cloudflare.com
Running Zig with WASI on Cloudflare Workers
https://blog.cloudflare.com/running-zig-with-wasi-on-cloudflare-workers/
50 comments
Nice! Good to see Zig adoption picking up. Fastly's Compute@Edge platform (https://developer.fastly.com/learning/compute/) has a section on "Custom SDKs" using languages outside of the supported SDKs (currently: Rust, Go, JavaScript) and in there you'll find Zig and Swift https://developer.fastly.com/learning/compute/custom/
I'm guessing there'll be a big uptick in adoption once zig passes 1.0. At least for me, I'm just waiting until the syntax stabilizes, so I don't have to change my code later on or relearn how to do things.
Could something like this - running in essentially a totally sandboxed environment, like webassembly - be Zig's sweet spot? I think of Zig as a very developer friendly, powerful, fast, fun, low level language, without the strict safety guardrails of rust.
For some applications, rust's borrowchecker is not something you're going to want to be without, but is "memory safety" actually a concern when compiling to web assembly? In that case, is Zig all pros and no cons? You get its easy to use interfaces for allocation and working with raw bytes, but without the chance of memory vulnerabilities, like when running in a sandboxed browser environment or Cloudflare's V8 isolates?
For some applications, rust's borrowchecker is not something you're going to want to be without, but is "memory safety" actually a concern when compiling to web assembly? In that case, is Zig all pros and no cons? You get its easy to use interfaces for allocation and working with raw bytes, but without the chance of memory vulnerabilities, like when running in a sandboxed browser environment or Cloudflare's V8 isolates?
Memory safety is also a helpful productivity tool, because it eliminates bugs, even if those bugs don't turn into security vulnerabilities. Whether that is worth the mental overhead of the lifetime/borrow system is something you have to decide. Personally, I find it's an easy choice.
> I think of Zig as a very developer friendly, powerful, fast, fun, low level language, without the strict safety guardrails of rust.
Whenever I read something like this, I think: "Why is it some people will dream up niches for other languages to fill that are about people not liking Rust?" It's interesting if only because it seems lots of people really like writing Rust. Have these people tried Rust? Shouldn't Zig stand on its own -- "Zig is better because it solves X and Y" or even some matter ease or style "Zig makes it easier to X and Y which is important for WASI apps"?
Whenever I read something like this, I think: "Why is it some people will dream up niches for other languages to fill that are about people not liking Rust?" It's interesting if only because it seems lots of people really like writing Rust. Have these people tried Rust? Shouldn't Zig stand on its own -- "Zig is better because it solves X and Y" or even some matter ease or style "Zig makes it easier to X and Y which is important for WASI apps"?
Zig does stand on its own
https://ziglang.org/learn/why_zig_rust_d_cpp/
https://ziglang.org/learn/why_zig_rust_d_cpp/
Exactly. "Zig: Because [these good reasons] for WASI/WASM" or even "I just prefer Zig to Rust" is a much better argument. My point was only -- "Zig: Because people don't like writing Rust" is kind of a weird case to make, when lots and lots of people seem to like writing Rust.
There's masochists in the world. A lot of people enjoy writing C++.
I do, despite its warts, because as polyglot developer I am able to use mixed code bases, so I get all the safety I need from managed languages, and only use C++ for what would be anyway unsafe code blocks.
.NET + C++/CLI, Java/Kotlin + JNI/NDK, JavaScript + C++ addons, and so on.
Many developers get lost trying to use an hammer for every nail.
.NET + C++/CLI, Java/Kotlin + JNI/NDK, JavaScript + C++ addons, and so on.
Many developers get lost trying to use an hammer for every nail.
"Why didn't you just write this in C++?" == "But can't we be miserable forever?"
Sandboxed environments prevent many of the worst effects of memory unsafety, though at best you'll still crash your program, and at worst you'll still leak or corrupt the contents of your process's memory. They also impose performance overhead relative to unsandboxed code. So it's less "all pros and no cons" and more "fewer cons, but fewer pros". Which can still be an improvement in general, depending on context.
Does the Wasm sandbox actually catch use-after-free? I thought it was just out-of-bounds accesses or something like that, but I mostly have no idea.
This is awesome!
Does Zig also have the memory safety guarantees of Rust? It seems like a much nicer language to learn and use IMO
Does Zig also have the memory safety guarantees of Rust? It seems like a much nicer language to learn and use IMO
You can't have Rust's safety guarantees without being at least somewhat similar to Rust.
There are attempts to achieve this in a simpler way (such as Vale) but right now you're better off using something with GC if you want that sort of safety.
There are attempts to achieve this in a simpler way (such as Vale) but right now you're better off using something with GC if you want that sort of safety.
No, it doesn't make many safety guarantees. The language and tooling is designed to make it easier to write safe code than languages like C, but the compiler doesn't enforce safety. The burden is smaller but it's still the programmer's job.
It does enforce spatial memory safety, at least, which is a large improvement over C (and to some extent C++).
It does not enforce temporal memory safety, which is the harder one. Rust does have that (but it has unsafe blocks due to the limitations), and GC does have that (but it has overhead).
Zig might add a temporal memory safety mode in some way eventually, but it will almost certainly add overhead. Example options for that: never release allocated blocks to the OS, or Type-After-Type (different allocation regions per type). Both add enough overhead that it won't be good enough for some use cases, but fine for others.
It does not enforce temporal memory safety, which is the harder one. Rust does have that (but it has unsafe blocks due to the limitations), and GC does have that (but it has overhead).
Zig might add a temporal memory safety mode in some way eventually, but it will almost certainly add overhead. Example options for that: never release allocated blocks to the OS, or Type-After-Type (different allocation regions per type). Both add enough overhead that it won't be good enough for some use cases, but fine for others.
Zig has raw pointers with pointer arithmetic/indexing [1], so as far as I can tell it doesn't enforce spatial memory safety.
I don't think spatial memory safety alone is a large improvement over C++ in particular, since most memory safety vulnerabilities nowadays are UAF. For C, maybe; I'd have to look at LazyFishBarrel to see how many bugs nowadays are spatial as opposed to temporal (I'd guess that temporal vulnerabilities are more common nowadays in C too).
[1]: https://ziglang.org/documentation/master/#Pointers
I don't think spatial memory safety alone is a large improvement over C++ in particular, since most memory safety vulnerabilities nowadays are UAF. For C, maybe; I'd have to look at LazyFishBarrel to see how many bugs nowadays are spatial as opposed to temporal (I'd guess that temporal vulnerabilities are more common nowadays in C too).
[1]: https://ziglang.org/documentation/master/#Pointers
I'm not an expert on Zig, but my understanding is that it also has safe slices, and pointers which disallow arithmetic, and encourages their usage in idiomatic Zig. I assume that's why this nice post describes Zig as having spatial memory safety:
https://www.scattered-thoughts.net/writing/how-safe-is-zig/
Agreed that UAF is the larger factor these days for C++. Those are just impossible to really get rid of in such a language, without the overhead of something like Type-After-Type (which in some cases is just too much).
https://www.scattered-thoughts.net/writing/how-safe-is-zig/
Agreed that UAF is the larger factor these days for C++. Those are just impossible to really get rid of in such a language, without the overhead of something like Type-After-Type (which in some cases is just too much).
Which Modula-2 and Mesa were already doing in the 70's, or NEWP/ESPOL in the 60's, or JOVIAL in the late 50's.
Hence why it would be nice if Zig would be a bit better in that regard.
Hence why it would be nice if Zig would be a bit better in that regard.
GCs don't enforce temporal safety, no GC will ever prevent a race condition. The fact that the ownership concept in Rust prevents use-after-free bugs is just almost a side-effect of achieving fearless concurrency.
You're right that Rust also prevents data races (which is great!), but "temporal memory safety" is usually used to mean use-after-free and related issues, not type-safe data races. That was the context here.
Neither does Rust prevent all kind of race conditions, only the very special case of data races in the same process memory.
Concurrency related races accessing external resources, nope.
Concurrency related races accessing external resources, nope.
No, it is similar to Modula-2.
Much safer than plain C, but use-after-free and double free will still get you.
Much safer than plain C, but use-after-free and double free will still get you.
No.
[deleted]
Cool tech - basically CGI for WASM. I can't think of a use case though, usually everything I do server-side requires some form of persistence. Any ideas?
You can persist data with their edge stores (see the sibling comment), or use it to transpose between different API schemas, or as an API gateway that fetches consolidates, consolidates, and caches multiple micro-endpoints, or use it to run Doom (https://blog.cloudflare.com/doom-multiplayer-workers/) or IRC (https://github.com/cloudflare/workers-chat-demo) a variety of smaller useful things (https://developers.cloudflare.com/workers/examples/)
I am not affiliated with Cloudflare, but Workers is by far my favorite cloud/serverless platform -- so much simpler and faster than Lambda, etc. (if your use case is simple enough)
I am not affiliated with Cloudflare, but Workers is by far my favorite cloud/serverless platform -- so much simpler and faster than Lambda, etc. (if your use case is simple enough)
You can persist data in the key-value data store, as durable objects or in the R2 buckets (S3 like storage). So that is not a problem. There are other issues with CF workers though.
Cloudflare Workers have a few ways to persist data, check out KV and D1
Don't forget Durable Objects, which is a more fundamental building block.
https://blog.cloudflare.com/introducing-workers-durable-obje...
https://blog.cloudflare.com/introducing-workers-durable-obje...
Oh, cool, so tinygo compiled stuff should also work.
I've only recently started to use TinyGo for my µController development and despite not being as featureful as its C SDK counterparts, I still prefer it by a long shot.
Does the existence of WASI mean that I can also run Rust programs on Workers, compiled to WASM (without the JS shim)? Does anyone know of any tutorials on this? It would be great if I could access the Request object, with all the headers, payload, etc from Rust.
Yes, when they initially announced this feature, they actually demo'd running an unmodified Rust program compiled to wasm32-wasi. It just takes the HTTP body as stdin and writes the response from stdout, though, so it's limited: https://blog.cloudflare.com/announcing-wasi-on-workers/
I also did this recently but with the shim approach: a small Rust program that needs to run a cryptography operation on some input[1], compiled to wasi and operating on stdin/stdout, and then invoked by a larger Typescript program invoking it with the proper stream. Worked great.
There is also a thing called workers-rs[2] which will let you write the entire worker in Rust, but it does not use WASI. WASI isn't really enough for anything beyond simple stdin/stdout pipelines without something "wrapping it" to provide the needed fds, env vars, arguments, etc. So instead workers-rs binds to the underlying Worker runtime primitives, the ones normal JavaScript uses, and exposes that -- but it does all of the mangling/bindgen/shim bullshit for you in the background. The shim is unavoidable, and always exists behind the curtain, but this approach lets you completely ignore it. workers-rs doesn't support every API, though (e.g. no R2 support.) In theory, assuming you ported the workers-wasi interface to Rust[3] yourself, you could then write a Worker in Rust using workers-rs that load WASI-conformant WASM programs (written in XYZ) and execute that program on the request object. Sounds confusing but I think you get what I mean.
[1] No, this operation wasn't provided by the WebCrypto API, as far as I'm aware, otherwise I probably wouldn't have bothered with the added complexity.
[2] https://github.com/cloudflare/workers-rs
[3] The underlying WebAssembly support comes from V8; WASI, then, is "just" an implementation of some very well known functions/APIs in the surrounding environment that the WASM program expects to exist, and behave in a certain way. So actually the entire workers-wasi framework is here, and if you ported this TypeScript interface to Rust, using workers-rs, you could invoke any WASI program similarly: https://github.com/cloudflare/workers-wasi/blob/main/src/ind...
I also did this recently but with the shim approach: a small Rust program that needs to run a cryptography operation on some input[1], compiled to wasi and operating on stdin/stdout, and then invoked by a larger Typescript program invoking it with the proper stream. Worked great.
There is also a thing called workers-rs[2] which will let you write the entire worker in Rust, but it does not use WASI. WASI isn't really enough for anything beyond simple stdin/stdout pipelines without something "wrapping it" to provide the needed fds, env vars, arguments, etc. So instead workers-rs binds to the underlying Worker runtime primitives, the ones normal JavaScript uses, and exposes that -- but it does all of the mangling/bindgen/shim bullshit for you in the background. The shim is unavoidable, and always exists behind the curtain, but this approach lets you completely ignore it. workers-rs doesn't support every API, though (e.g. no R2 support.) In theory, assuming you ported the workers-wasi interface to Rust[3] yourself, you could then write a Worker in Rust using workers-rs that load WASI-conformant WASM programs (written in XYZ) and execute that program on the request object. Sounds confusing but I think you get what I mean.
[1] No, this operation wasn't provided by the WebCrypto API, as far as I'm aware, otherwise I probably wouldn't have bothered with the added complexity.
[2] https://github.com/cloudflare/workers-rs
[3] The underlying WebAssembly support comes from V8; WASI, then, is "just" an implementation of some very well known functions/APIs in the surrounding environment that the WASM program expects to exist, and behave in a certain way. So actually the entire workers-wasi framework is here, and if you ported this TypeScript interface to Rust, using workers-rs, you could invoke any WASI program similarly: https://github.com/cloudflare/workers-wasi/blob/main/src/ind...
I see, thanks! Sounds like WASI is pretty minimal, then, and we're basically going back to CGI, but it might be good enough. workers-rs looks interesting, though, thank you!
Technically it never went away! FastCGI is still plenty good enough for deploying lots of stuff with PHP. :) And I think the reason people like these new "serverless" things is exactly because, like CGI, it feels pretty developer oriented. Request goes in -- response comes out. Hard to beat that in a way. The no-management part is the real sell, though.
PHP really did have a much better developer experience, put a file where you want your view to be, done. Can't beat it for small things.
I'm not sure if there's a JS shim or not, but rust has been a supported language for a long time with that workflow: https://developers.cloudflare.com/workers/tutorials/hello-wo...
There's a shim there, yeah (worker.js) :/
Yeah, that's kind of cheating... it runs Rust after compiling it to WASM and running that inside a JS runtime! Anyone thinking of using that should definitely consider just using WASI instead as Rust, as Zig, can compile to WASM/WASI (for those who don't know, WASI is like a POSIX for WASM, so WASM-compiled programs have access to a POSIX-like API, and therefore don't depend on the Web APIs like the Rust-compiled-to-worker solution does).
"Anyone thinking of using that should definitely consider just using WASI"
As far as I know, CFW heavily leans on V8 isolates for scaling.
Why reinvent the wheel?
As far as I know, CFW heavily leans on V8 isolates for scaling.
Why reinvent the wheel?
I'm uninformed. So cloudflare has workers which run wasm and the article is about zig->wasm?
Why not use linux containers?
This should give you a good idea: https://blog.cloudflare.com/cloud-computing-without-containe...
We (Wasmer) have been working with the Zig team (special thanks to Topolarity here!) to get Zig on WASI and WAPM! We also want to have it automatically deployed to WAPM on each new version released.
For anyone that wants to try Zig online, this might be of your interest! https://wapm.io/topolarity/zig