The async design changed multiple times, and the current approach is that of requiring passing an I/O interface that encapsulates the logic, same approach as for passing around an allocator.
This effectively makes all I/O bound code "quasi-async", and it's all explicit and the caller is in control.
As far as I understand it, the idea is that the IO interface is "magic" in the sense that it affects which version of a function gets called: either the synchronous, linear version, or the asynchronous, stackless, suspensible state machine version.
This has a lot of implications, especially for function pointers and other forms of dynamic dispatch.
My personal take is that async code is fundamentally different from sync code, because the weights of various tradeoffs change dramatically. In my view, "colorless" functions are not really a worthwhile goal in the first place. But I've been wrong before, so it may turn out to be great, we'll see.
There is no magic about the I/O interface. It's a regular dynamic dispatch, the same as if you use virtual functions in C++. Currently, and in the foreseeable future, there are only two options how it can run, blocking mode where each task occupies a dedicated thread, or using stackful coroutines. The interface implements blocking functions, there is no async waiting anywhere in there. But because it's dynamic dispatch, it allows the async implementation to do the "prepare operation, suspend until completed, wake up" dance, where the threaded implementation would just run the blocking syscall directly. There are plans to add stackless coroutines, but that will require many language restrictions to happen first, so it's not going to happen any time soon and when it does, the interface will likely change to accomodate it.
> it allows the async implementation to do the "prepare operation, suspend until completed, wake up" dance, where the threaded implementation would just run the blocking syscall directly. There are plans to add stackless coroutines
Yeah so that's a lot of hidden magic and implicit logic. ¯\_(ツ)_/¯
"It's actually explicit if you look at how it's implemented" is true for everything.
If you look at how it's implemented you can see that the classic "XOR a register with itself to zero that register" trick is in fact basically free in a modern CPU rather than using up the ALU because of how register renaming is implemented. But hopefully we're not pretending that "You could buy the CPU vendor's technical manuals" makes this now explicit.
In most languages, async is implemented as a special feature in the compiler, not as userspace code. You can't just jump-to-definition of async/await keywords in languages that use them. async/await are not keywords in Zig, they are regular function calls. There is obviously a categorically different level of insight between jumping to library code and digging into compiler internals.
While you could say that, all the operations are documented behavior. You need to assume they will block. If the blocking happens in userspace runtime or in the kernel doesn't matter. And you, as the application author, are the one choosing which runtime to use. This just allows the libraries to be generic.
const std = @import("std");
fn performTasks(io: std.Io) !void {
var group: std.Io.Group = .init;
errdefer group.cancel(io);
// Launch concurrent background jobs
try group.async(io, simulateWorker, .{ io, 1 });
try group.async(io, simulateWorker, .{ io, 2 });
// Wait for all group tasks to complete
try group.await(io);
}
fn simulateWorker(io: std.Io, id: usize) void {
std.log.info("Worker {d} started...", .{id});
// Any blocking operation (like sleeping) routes through 'io'
// Under the hood, this will either block an OS thread or yield an event loop stack.
io.sleep(.fromSeconds(1), .awake) catch {};
std.log.info("Worker {d} finished!", .{id});
}
pub fn main() !void {
var gpa_alloc = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa_alloc.deinit();
const allocator = gpa_alloc.allocator();
// Initialize the Threaded I/O backend
var threaded = std.Io.Threaded.init(allocator, .{
.async_limit = std.Io.Limit.limited(4), // Max concurrent tasks in the pool
});
defer threaded.deinit();
// Extract the generic I/O interface pointer
const io = threaded.io();
std.log.info("Running on Threaded backend:", .{});
try performTasks(io);
}
pub fn main() !void {
var gpa_alloc = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa_alloc.deinit();
const allocator = gpa_alloc.allocator();
// Initialize the Evented (non-blocking) backend
var evented: std.Io.Evented = undefined;
try evented.init(allocator, .{
.thread_limit = 4, // Worker threads running the event loop
});
defer evented.deinit();
// Extract the exact same generic interface
const io = evented.io();
std.log.info("Running on Evented backend:", .{});
try performTasks(io);
}
std.Io.Threaded and std.Io.Evented are two types of IO backends. std.Io.Threaded uses blocking IO primitives and Evented uses asynchronous IO primitives. Switching between backends is done via dynamic dispatch, the used backend is passed in via an explicit function parameter.
The only real question you should have is, how does the "non-blocking" case work when the interface is still a synchronous call? Well, the answer is quite boring... You just call into the scheduler and it runs the other task inside the blocked operation.
I was promised hidden magic and implicit logic! Now I want a refund!
Doesn't that assume there are no intrinsic differences between single-threaded, multi-threaded and async execution? Plenty of code works fine single-threaded but breaks once it's run multi-threaded or async, and some works fine multi-threaded but breaks under async. With explicit async you have precise control over where you yield back to the event loop. With Zig's Io you don't get that unless you deliberately design for it, and at that point you've just got an implicitly coloured function.
There is a difference between single-threaded and multi-threaded, but if you use `io.concurrent` or `io.async`, you are expected to have your code assume multi-threaded enviroment. And there is no visible behavior change between blocking and non-blocking backend, assuming you are not calling external libraries that do their own I/O. So code written and tested on blocking threaded I/O, will work just fine on async implementation. All I/O operations are the yield point.
I bet it would be the NVIDIA, at least initially, not for the encryption capability per se, but for simple fact that adapting to TPU at high performance is non-trivial.
Cpp2 has been dead for a while afaik, while Carbon is still going.
I don't think Carbon is dead, it just all depends on how easy it actually is to rewrite "all of C++" in Rust. (The jury is still out on this one, but it's not looking good.)
Yes, but at least it was a real thing with a serious proposal behind. Even if Carbon ships, what value would it give in 2029 or whenever it is in a world where writing rust or heck even C++ is now way easier and foolproof (as long as you know where to guide an LLM)
The Carbon team has always been the first one to assert this is for Google internal purpose first and formost, everyone else should go to Rust, Java, Go, C#, Swift,... whatever fits their workloads.
It has been the social media that has given Carbon a roadmap that the team never communicated.
As for Cpp2, it was yet another C++ wannabe replacement, sold as if it wasn't, because the chair of ISO C++ at the time, naturally could not be seen as yet another one coming up with C++ wannabe replacements as well.
Yeah, sure, but you don't need a dedicated plan mode for that at all. you can just do it in auto mode, and say "let's do some planning first", and Claude will (nowadays) be smart enough to understand that it's not supposed to jump straight into the implementation.
So again: You don't need plan mode, auto mode works just fine, there is no difference in the workflows here.
My understanding is that this one was never really "official", and that the guy who released it wasn't following standard procedures. That's not even the official Google github account.
The reality with releases like this is that I'm 90% sure most Google bigwigs have never heard of it, and it's misleading to label it as "Google's" in the title.
Yes, it was developed by Google employees, that does not imply it has the full backing of Google, or Deepmind, or GCP. Notably, the website doesn't seem to claim this either.
Fair enough, I missed that specific line. The point still stands that I wouldn't expect this to have GDM leadership backing. (If you're planning to use this at all, that matters for how much faith you should have in the product.)
I have no insider knowledge but https://x.com/rakyll is working on it and she is tweeting about it and I got the impression there is a quite a team behind it. It looks like an effort in GCP.
Yes, and one thing to understand about Google is that no AI framework or tool is guaranteed to survive unless it has the explicit backing of Google Deepmind.
"Effort in GCP" is a red flag. (See Gemini CLI, which was shut down in favor of Antigravity CLI.)
Disclaimer: I'm one of the co-creators of this project.
AX is a layer that is closer to job orchestration, Agent Substrate. It's NOT an agentic framework. We use Antigravity for a few generative features but are abstracting away some of these components so anyone can bring their own. AX is trying to solve some tedious things everyone has to deal with. Layering execution with the underlying stateful worker, wiring up the network correctly, providing sub "task" identity, provisioning the right environment, proving stateful branching, auto discovery.
We want to keep Agent Substrate free of generative features and still need a layer above for some integrations that require agentic concepts and identity.
Google compute services are under GCP and I work on Kubernetes. So the comparison with Gemini CLI is not relevant here.
And anyone who bothers to just do a side-by-side feature comparison can immediately see how many features antigravity is still missing compared to Gemini CLI even today.
Substrate is in the process of being donated to the CNCF as a vendor-neutral common ground (similar to K8s). (The agent-substrate org is currently Google's, but that will change).
Yeah, anyone should think of this as an experimental side project that may be forgotten in 15min and make careful decisions about using them in production environments.
We have used a few of Google's (smaller) open source projects, and in the last 2-3 years most of them are getting fewer updates if any updates at all. Some became very bad tech debt and we had to spend a lot of time migrating them.
Of course, that is the nature of open source projects (written in the license terms), and there is nothing to complain. But it's important to point out these days Google's open source project are not any more trustworthy than a one man's project in terms of support and maintainability. Personally I would stay away from them as far as possible. Especially if you look at what happened to Android, Gemini CLI etc.
(To be honest, even if it were officially supported by Google, that barely means anything. https://killedbygoogle.com/)
In this case it arguably helped. I do wonder how much longer that stalemate would have gone on without the blow up.
I don’t think it’s a good policy in general. Mostly I think it would just end up in alienation and people not wanting to work with you. And Martin did leave. But he had a point and it seemed to get resolved.
The async design changed multiple times, and the current approach is that of requiring passing an I/O interface that encapsulates the logic, same approach as for passing around an allocator.
This effectively makes all I/O bound code "quasi-async", and it's all explicit and the caller is in control.
reply