Posts in Rust (20 found)
Corrode Today

The Rust Foundation

Most Rust developers use the language, compiler, package registry, and tooling every day without thinking too much about the organization that helps keep parts of that ecosystem funded and sustainable. This episode is a re-introduction to the Rust Foundation: what it does, what it does not do, how it relates to the Rust Project, and why that distinction matters for teams using Rust professionally. My guests are Rebecca Rumbul, Executive Director and CEO of the Rust Foundation, Lori Lorusso, Director of Outreach at the Rust Foundation, and David Wood, Principal Software Engineer at Arm, Compiler Team Co-Lead in the Rust Project, and a Rust Foundation board member. Together we talk about the practical side of ecosystem stewardship: infrastructure, security, interop, maintainer support, governance, corporate membership, open-source funding, and the pressure new technologies like AI put on language ecosystems. CodeCrafters helps you become proficient in Rust by building real-world, production-grade projects. Learn hands-on by creating your own shell, HTTP server, Redis, Kafka, Git, SQLite, or DNS service from scratch. Start for free today and enjoy 40% off any paid plan by using this link . The Rust Foundation is an independent non-profit organization supporting the success, sustainability, and positive impact of the Rust programming language. Its work includes funding and supporting ecosystem infrastructure, security and interoperability initiatives, maintainer support, project administration, community programs, events, and collaboration with member companies and donors. The Foundation is separate from the Rust Project. The Rust Project governs the language, compiler, standard library, and technical direction through its own teams and decision-making processes. The Foundation provides organizational, financial, legal, and operational support around that work, without owning Rust’s technical roadmap. Rebecca Rumbul is the Executive Director and CEO of the Rust Foundation. She leads the Foundation’s work on organizational strategy, member engagement, sustainability, and support for the broader Rust ecosystem. Lori Lorusso is Director of Outreach at the Rust Foundation. Her work connects the Foundation with the Rust community, member organizations, trainers, contributors, and companies adopting Rust in production. David Wood is a Principal Software Engineer at Arm, CE-SW Rust Team Lead, Compiler Team Co-Lead in the Rust Programming Language Project, and a board member of the Rust Foundation. In this episode, David adds the perspective of someone involved in Rust’s technical work as well as Foundation governance. Mozilla - The first home of the Rust language Python Steering Council - The governing body of the Python Project How to Write a C++ Language Extension Proposal - Bjarne Stroustrup, the inventor of C++, on why C++ needed a standards committee SCRC - The Safety-Critical Rust Consortium FLS - The Ferrocene Language Specification, a specification of the Rust language that is required for certain steps in the certification of Rust for safety-critical applications Foundation Membership Tiers - The different quantifiable benefits from Diamond to Silver and Associate Memberships Rust Commercial Network - A group of organisations that use Rust in production working together with the Rust Project Rust-C++ Interoperability Initiative - An initiative of the Rust Foundation to improve interoperability between Rust and C++ Rust Embedded Working Group - An official working group of the Rust language to improve usability of the language in hardware-constrained environments An AI Security Engineer in Residence for the Rust Ecosystem - Describing the position of the security engineer made possible by funding from Alpha-Omega Rust Foundation Maintainers Fund - The Foundation’s fund to support Rust maintainers Rust Foundation Trusted Training - The Foundation’s accreditation program for Rust training providers Rust Foundation Website Rust Foundation Media Room Rust Foundation on GitHub Rust Foundation on LinkedIn David Wood’s website

0 views
Martin Fowler 3 days ago

Fragments: July 13

Some more of my notes from Thoughtworks Future of Software Development Retreat . When we had our first retreat in Utah early this year, nobody had heard of Harness Engineering . This time we had a whole session on it. When comes to the guide side of harnesses, most of the discussion is about context management. While context windows have increased is size as models get more sophisticated, that doesn’t mean that models will properly focus on the right bits. Models typically only focus attention on part of the context, and to get the best behavior, we need to manage that focus. One attendee keeps their context small, limiting the file to less than 200 lines On the sensor side, we see more attention on computational sensors. Two patterns from one participant was shifting to languages with greater controls, (eg Rust rather than Python) and “leveling up” validation approaches, using more property-based testing and techniques from formal methods. One commented that while they aren’t smart enough to write specifications in a formal specification language, they are smart enough to read it and check it makes sense for their domain. Will our attention on harnesses last long enough for our next retreat? Will the models just get so good that harnesses become unnecessary? Those with some mechanical sympathy for LLMs seem to think not - but are they overly coupled to the current state of technology? I find such speculation tends not to lead anywhere useful, I’ve not seen much success in guessing the future in the past, and with technology as radical as this, I don’t see it being any easier. So for the moment, attention to harnesses pays off. We find it reduces token usage, and also allows weaker models to be useful, supporting such things as local hosting of open-weight models. ❄                ❄ Which naturally segues me to a session on self-hosted models. Increasing token costs have made hosting an open-weight model more attractive, particularly due to the decreasing time for open-weight models to catch up with frontier models. Cost isn’t the only factor, however, many folks find a desire to be independent of the frontier model firms to be the the driving force. After all we’ve seen the U.S. government intervene to deny access to models, increasing the desire for greater model sovereignty. Information security is also something to consider, some attendees just can’t give models necessary data for critical work. Even without that, if someone else hosts the model then their model learns rather than your model. And although recent events have increased interest, several participants worked with companies that had been self-hosting for up to a couple of years. Is this trudging down the same path of self-hosted clouds, which led to lots of folks spending excessive funds on half-arsed private clouds ? The answer hinges upon whether it ends up being simpler to host a model than a cloud, perhaps due to a simpler interaction protocol. The hard part of this may be the talent required to efficiently use the GPUs, managing an inference data center currently isn’t a widely available skill. Even self-hosted models are a cost to operate, capital costs in GPUs, ongoing costs in electricity. The physical design of a data center can affect optimal usage. There’s an opportunity here for professional services firms to help companies manage this. Cost control also involves teaching people to pick the right model for the job. Can we teach engineers, or indeed other users, to pick a less-powerful model? This, of course, could be a job for model itself, acting as a broker, deciding which model is the best choice to tackle certain jobs. Self-hosting may lead to a greater use of fine-tuning. Currently that’s a niche activity, but over time we could well find that models that are fine-tuned to a particular domain need less reasoning, consume less tokens, and thus are cheaper to operate. We are seeing models trained specifically to support programming. As with any topic with this degree of uncertainty, the big win isn’t finding the right answer, but coming up with a strategy that will cope with the inevitable and unpredictable changes. ❄                ❄ After an event like this, many people come up to me and ask me to make some grand summing up. I hate this, because I rarely leave these kinds of event with some grand narrative. Even after mulling on it afterwards (in writing the above notes) I still usually don’t have one, and distrust one that forms, as my skepticism includes attempts to make coherent narratives of an event that’s naturally rather jumbled. However my failings are irrelevant this time, because Kief Morris has put together such a narrative, and it’s a convincing one , even to a narrative-denier like me. The sessions had different titles and different casts, and on the surface they were about different problems. But they weren’t. Nearly every one of them was a different facet of the same argument. How much do we let an agent decide, and how do we stay confident in what it does? He looks at code review, questions whether it matters, but sees that the rigor that many associate with code review shifts to other forms. He describes the disagreements about how much we should trust an agent to identify and fix production incidents. He sees that the contrast between how much leeway teams give to agents depends on the context they are operating Underneath all of these sessions, the operations debate, the wide-remit team, the dark-factory spectrum, the argument about who’s allowed to steer the model, people were making the same handful of choices over and over about a single thing: the unit of work they were prepared to hand to an agent. How big it is. How much of the job it covers. What you do to get it ready to hand over. How you check what comes back. What you put around the agent to keep it inside the lines. Different rooms set those differently, but they were setting the same controls. ❄                ❄ Sam Ruby convened a session called “Bring me a Rock”. The name evokes a particular kind of management dysfunction. The manager tells his underlings to bring him a rock, and then starts rejecting the results without explaining why (“no not that one”, “no not that one”) until eventually one rock matches the unstated expectation. It names a manager who substitutes serial rejection for the work of saying what they want, and makes you pay for their unfinished thinking one rock at a time. Sam had already written why he thought with LLMs, this changed from a slur to a defensible way to work . When its a bunch of tireless machines with endless patience, that return new rocks in minutes rather than days, then an approach like this (using the brainstorming register becomes a defensible way to work. Sam described the discussion : The room pulled it somewhere narrower than I’d framed, and the narrower place was the more interesting one: not how to explore by elimination but who should even be allowed to. Product managers, increasingly people managers, are reaching for these models directly, and seasoned engineers get measurably better results from them than untrained people do — so the worry followed. If expertise is what separates a good outcome from slop, should non-engineers be steering the model at all? It’s a fair question, and I think it’s the wrong one, because it mistakes the act. When a manager reaches for an LLM instead of routing the work to the team that reports to them, they didn’t pick up a tool — they made a hire. And you don’t ask permission to manage your own team; a manager who decides a piece of work is better given to a new participant than to the existing one is doing the most ordinary thing a manager does. Framed that way, the permission question dissolves into an older, better-understood one — the one Drucker named in 1959: when the worker knows more about the specifics than the manager does, you manage by objective, not by method. The non-engineer steering an agent is exactly that manager, out-known by the thing they’re directing, and the slop the room feared is the old danger of managing by method when you should be managing by objective. The question isn’t may they hire? It’s do they know how to manage by objective? — which you can teach, hire for, and hold people to without anyone first becoming an engineer. Sam’s article explores managing an LLM by objective, giving it a goal rather than a task. And Kief’s earlier point about the essence of the discussion still holds: how confident can we be that it’s done the right thing? We can outsource many things, but not the acceptance criteria, at some point there’s a human request and a human judgment on whether that request was properly executed. But the danger lies in important unstated objectives, unstated perhaps because they weren’t even imagined. It’s easy to state objectives around desired functionality. Give me a an application that will examine my emails and form a todo list for today. But behind that simple statement is a thicket of unstated assumptions. We tend to assume The Genie won’t include any undesired functionality, perhaps deleting emails it thinks are unworthy of our attention. We assume it won’t let an email tell it to send private information to [email protected]. We have some hope here - we hear more experiences that suggest that recent models can do an excellent job of finding (and hopefully fixing) security holes. The careful precision of the machine outruns the sloppy if imaginative thinking in squishyware. Perhaps we can assume the genie can take care of some of our unstated objectives. Conformance tests (sensors) are more valuable than specifications (guides), but it’s hard to imagine all the conformance tests that are needed to say what shouldn’t happen. Furthermore, building software is about exploration, finding out how a workflow can evolve as machines are embedded in the process. For a human to guide that process, we need some understanding of it. My sense is that model building is still important, and while I agree that the genie can take an active role in that construction, I don’t think the human can entirely outsource it. Even if the genie builds the model itself, it needs to teach us that model, because the model helps us imagine and communicate the goals, the objectives that we give to the machine. ❄                ❄                ❄                ❄                ❄ If you follow my feeds (which you probably do if you’re reading this), then you’ll know that Birgitta Böckeler has written a couple of memos on working with local models. She first looked the factors that influence how viable they are for programming , and then related some of her recent experiences evaluating such models . As a nice, if accidental, complement to these, Sebastian Raschka wrote a detailed guide to his local model environment . Like Birgitta, he’s found the Qwen 3.6 model to be the current sweet spot for local agentic programming. ❄                ❄                ❄                ❄                ❄ Simon Willison shares a useful tip to save money while using the latest Anthropic Fable model Tell Fable to use other models for smaller tasks, applying its own judgement about which model to use. ❄                ❄                ❄                ❄                ❄ Josh Comeau writes a blog and online courses for developer education, primarily front-end web material. His been successful for most of this decade but has found his online courses have had only ⅓ the sales this year . He attributes this to AI, partly as people worry if it’s worth spending money on a job that may not have a future, but also because AI can provide personalized tutoring. ideally, it shouldn’t cost any money to learn stuff. But I sorta worry about how this is supposed to work, going forwards, if there’s no incentive for people to make high-quality free content. I’ve spoken to a few course creators now, and we’re all seeing the same trend. Revenue down 50%+. Fewer people engaging with our content. People switching to LLMs, which slurp up all of our work and regurgitate it, without consent or compensation. It feels pretty bleak. 😅 ❄                ❄                ❄                ❄                ❄ John Gruber is annoyed that Claude’s desktop app for MacOS in uses Electron . Electron guarantees that an app feels just as wrong on all platforms. He has some tasty invective for the folks at Anthropic with ties to the Electron platform. Finding out that one guy — who is a senior Electron maintainer — has led the teams for the desktop clients for Slack, Notion, and now Claude is like discovering that it was one guy — whose family business was a distillery — who helmed the Titanic, piloted the Hindenburg, and then served as air traffic controller for Amelia Earhart. The deeper question here is whether there should be a future for cross-platform front-ends in the world of agentic programming. There’s lots of evidence that coding agents do a great job of building the same thing in multiple languages and platform ecosystems. That should mean that the days of least-denominator cross-platform UIs are numbered - and that number is small. ❄                ❄                ❄                ❄                ❄ Dan Davies tries to draw a distinction between interactional and contributory expertise . Contributory expertise is that held by people who are doing the work to advance a field of study, interaction expertise is held by folks that spend time talking to contributory experts, building up a decent store of knowledge themselves, but not steeped in the day-to-day of the work. it seems to me that there is an important distinction here, which is not any less important because the dividing line might be difficult to establish empirically, or even if that line turns out to be in a different place from where we guessed it was. As well as difficult cases where it’s not clear, I think we could also come up with cases where the distinction between interactional and contributory expertise would suddenly become very clear and important indeed – the ones where someone who was faking it got “found out”. And so the question that I think is quite important is whether there is a similar kind of distinction between the kind of expertise that it’s possible for a machine to get by industralised consumption and interaction with a much larger corpus of literature than any human being could inhale, and genuine contributory expertise that could apply to entirely new situations outside that literature. As a human, I’d like to think I’m more of a contributor than an interactor (especially given my increasing introversion), and thus relatively safe from being forced into obsolescence by silicon. But I’m also aware that my career is devoid of any original ideas, my skill is only that of someone who is good at selecting and explaining the ideas of others. (As Brian Foote put it more memorably: “an intellectual jackal with good taste in carrion”.) But there’s skill in being a good jackal too - and we don’t really know yet where the real boundaries of the LLMs will lie.

0 views
seated.ro 4 days ago

You fail to learn if you don't learn to fail

If all your time is spent watching output tokens, where do your input tokens come from? Letting an agent rip on full auto is basically doom scrolling. Even worse if you're doom scrolling while the agent runs. We humans love frying our dopamine receptors. This feels great until you realize what you were offloading: the struggle. The part where you fail. Failure is the entire point. You don't make progress in the gym unless you take a set at least close to failure. The muscle only adapts when it's forced to. It is no different for the brain. It is very hard to admit to yourself that your skills have atrophied. It is even harder to admit this to other people. I will admit that over the past several months my brain has gotten smoother (and I wasn't even on Twitter much!). Recently, I had written an abstraction for my diff viewer ( diffy ), an element system with a macro that lets agents write html-like code in rust for native ui (they reason better with this). But it wasn't adopted everywhere in the repo yet, so when I asked for a new feature, the model decided to hand paint it straight to the viewport instead. Every behavior the element system gives you for free was just... missing. Text wasn't selectable. Hover highlights wouldn't go away. And since I wasn't looking closely, it iterated on the slop and produced more slop, more bugs. I just kept saying continue. I lost a whole day untangling it, and the funny part is that once I actually looked at what it had built, every bug was the same bug. When you hit a roadblock and your immediate reaction is to reach for something else (previously, this used to be other people, but now it is a language model) you are essentially skipping the part where you actually learn to solve the problem. It is funny how one of the best "learning tools" has turned out to be the number one cause (anecdotal. sue me) of the lack of learning! It's been a few months since I started writing this, and things have gotten more dire. Several major software services barely work now, grown engineers I once respected are writing somber posts about missing a language model that was banned for a while. Mourning. For model weights. It's all so dystopian. As the agents get better, one is basically expected to produce code at an alarming rate. The timeline to get something done is compressed but the time it takes to come up with solutions to hard problems has not. There are usually a few good abstractions one can come up with that balance the upsides and tradeoffs for most software problems. However it is currently trivial to turn your brain off and let the slop flow. The code will be complex. It might look like it all works, but something always breaks. And the solution to that? More slop. Software quality is collapsing as a result, and the societal expectation that engineers understand what they ship is disappearing. You never understood the code in the first place. So when you need to change it, you're asking the same stateless clanker to modify code it has no memory of writing. All output tokens and zero thinking tokens. A lower barrier of entry to write software doesn't imply the standards for good software must be lowered. The growing trend is to do things because you now can (supposedly), but we used to try and do things because we could not out of sheer stubbornness. Carmack and gang shipped QuakeWorld with client-side prediction over dial-up when the conventional wisdom was that twitch shooters over the internet were unplayable. This only happened because Quake's original netcode was laggy and everyone hated it. (They fixed it in a month.) George Dantzig arrived late to class, mistook two "unsolvable" statistics problems for homework, and solved them. Nobody told him they were impossible, so he just did the work. Andrew Wiles spent seven years alone in his attic working on Fermat's Last Theorem, a problem mathematicians had given up on for 350 years. He announced the proof, a reviewer found a hole in it, and he spent another year fixing that too. Notice that all three of them became who they are because of the struggle, not despite it. The people benefiting most from generative tools today, say Terence Tao or Mitchell Hashimoto, already put in the time, so when they offload work they're just skipping the typing. When people like you and me (if this is not you, then I apologize) offload, we skip the grind itself. With language models, easy tasks got easier, hard tasks stayed hard. The hard part was never the task itself. I don't know, I am figuring this out as I go. The amount of time I have spent actually programming has been dropping month over month this year. I used to have a coding stats section on my website that would track hours I spent writing code split by language, recently I had updated it to this: and it made me quite sad. I do think that sometimes all you need is to realize that the thing you are doing is actually detrimental to your growth. Consistency matters more than one would assume. If you consistently take some time away from these tools and actually use your brain, that alone is already significantly better than offloading your thoughts. Solve the problems yourself. Or at least try, fail, and spend time thinking. There is seemingly no "learning" phase anymore. You are expected to just know things. Learning is fun, don't let anyone take this away from you. I've written about this before . It is probably going to be slow, learning takes time and effort. You will feel stupid (I feel stupid). This is a good feeling, because there exists a world where you are no longer stupid and the path towards it is learning. Books still exist! Libraries are still open, notebooks waiting to be written in. Read more. Write more. If you really do care about improving yourself, be honest and use these models for what they are, highly efficient filters of zettabytes of data (the internet is estimated to be 175-240 zettabytes ( 10^{21} bytes)). It was extremely difficult to identify what one needed to read to learn niche topics even like 2 years ago. I remember asking a good friend of mine to recommend material to dive deep into learning about SIMD, and honestly there wasn't much stuff to read except the Intel Intrinsics Guide. And if you've ever taken a look at that, it is quite cancerous for a first-time reader. Language models are super useful here because you can point them at material and you can ask questions that pertain to the thing you care about and it will simply just tell you the correct things. One good thing in this age of slop is to consume knowledge at an unbelievable pace. I don't necessarily mean using only model output for learning (I don't trust them to learn any topic more than a shallow amount), but rather using them to help sift through the plethora of information available out there and identifying the right things to read. Human slop exists too and using a language model to supplement your learning might help keep you sane (ironically). I like using these models to write code that I tell it to write (outside of work I enjoy doing it myself entirely), and I am largely disinterested in asking it what I should write. There are exceptions of course, because not everyone is working on scaling software services which has largely been solved (but slowly being forgotten), but that would be for you to decide. The best model you have access to (and it has solved continual learning) is, and always has been, the one inside your skull. It's time to scale up its input tokens.

0 views
Ankur Sethi 6 days ago

Data locality (sometimes) beats algorithmic complexity

I've been ECS -curious ever since I learned about it in the Bevy game engine documentation . The ECS architecture predictably improves performance in languages that give you low-level control over memory (C, C++, Rust, Zig, and friends). But how does it fare when used in high-level, dynamic, garbage-collected languages such as JavaScript? This is the question Dan Murphy set out to answer in The Physics of Memory : Is it possible to use an ECS-style architecture in Javascript? And for applicable operations, does that actually do better than objects + V8’s garbage collection? To answer the question, Murphy built a 2D physics simulation of 15,000 balls bouncing around in a box using several different techniques. He found that a JavaScript implementation of the simulation that used ECS outperformed the usual "giant graph of objects" OOP implementation by 24x. He writes: It's also worth noting how the usual OOP implementation creates GC pressure: In OOP, entities are scattered across the heap. As they move and interact, the JavaScript engine’s garbage collector is constantly triggered, and the CPU frequently stalls waiting for pointer lookups. This causes sporadic frame drops (micro-stutter). Because ECS uses pre-allocated, flat TypedArrays, memory access is 100% predictable and GC overhead is zero, guaranteeing perfectly smooth frame delivery. My favorite thing about Murphy's post is that you can run all his benchmarks in your own browser. I love it when technical explanations or benchmarks are accompanied by embedded "apps" you can play around with. I'm surprised at how much data locality matters for performance. An algorithm with worse big-O complexity can outperform one with better complexity if it makes good use of the CPU's L1/L2 caches. Very cool. Cache Locality > Algorithmic Complexity : At 15,000 entities, pointer-chasing and unpredictable tree branching cannot compete with the contiguous L1/L2 cache locality of a flat 1D array sort—even though trees have a better theoretical Big-O complexity. You Don’t Need WASM for ECS Wins : Simply switching your JavaScript codebase to a flat Structure of Arrays (SoA) layout yields up to a  24x speedup  over OOP. WASM is the cherry on top (another 2.5x), not the entry ticket. Pragmatism Wins : While a hand-tuned SoA is the absolute fastest, using a production ECS library like   still gives you a massive  14x speedup  over OOP while providing a clean, scalable API. IMO, for 99% of applications using a library is the correct engineering choice.

0 views

Why I think Rust is Object Oriented

Before we begin, I want to say I don't care that much if you disagree with me. There's no sound precise mathematical definition of Object Oriented, no ISO standard, and no grand arbiter who decides what is and what is not OO (although I believe Casey Muratori may have applied for that position somewhere in his 12 day monologue about the subject). You are unlikely to change my mind, and I am unlikely to change yours, and that's perfectly fine. On with the post! In my heart of hearts, Rust feels like an object oriented language. Practically everything I write in Rust is a datum + set of behaviours intimately associated therewith. I hide my struct fields, give my totally-not-objects equality semantics and string representation. I utilise polymorphism via traits - and while I know that traits are not technically interfaces, that fact occupies the same region of my brain that knows that mandrills are not technically baboons. Almost all my functionality is written in methods, and those methods belong to the data they operate on, in a way that they definitely don't in something like C or OCaml. But what about inheritance? OO-haters often fixate on this as the defining thing about objects, which has always perplexed me. I came up in the JavaMania era and "composition over inheritance" was the accepted wisdom of every programmer who took OO seriously. I scarcely used it during my time in the C# mines. Historically speaking the case is weak as well; neither the first smalltalk, nor the first simula had inheritance. Self was hugely influential (traits originated in Self) and didn't have it either. I won't deny it wasn't a common feature, but it never took center stage in my mind outside the brief "Cat inherits Mammal" phrase we all go through, and it certainly wasn't encouraged in the OO design books I read. So why the disconnect? Why does talking about this not resonate with other Rust programmers? I think because in the Rust culture, "objects" are something very different. They're virtual destructors & inheritance trees. They're a nasty thing C++ had that Rust forwent because dynamic dispatch is slow (except in Zig of course where it's fast now). Of course Rust isn't OO! But for those of us who took object-oriented design seriously, everything you read just reinforced that they're neat little black boxes you call methods on. And of those, rust is full.

0 views
Brain Baking 1 weeks ago

Favourites of June 2026

The beginning of this month marks the official end of my own company. After just two years of establishing and owning Brain Baking BV , the notary ended it. There have been no professional activities related to the company since I switched back to education in December so for me it made little sense to keep that door open only for the monthly administrative costs to pile up. I hope to build a bit more stability this time around, both on personal and professional level. My statute as lecturer has been extended for a year: so far, so good! Previous month: May 2026 . A few very short ones and one quite big one that I ended up enjoying very much. DreadXP, the developers behind Dread Delusion , also recorded dev diaries on YouTube: Related topics: / metapost / By Wouter Groeneveld on 3 July 2026.  Reply via email . The Aching —a Sierra On-Line-like adventure game weighing less than that runs on any 8086 machine. It also happens to be good, even though it feels more like an introduction of this horrified world. Serious Sam: The First Encounter —I started replaying this two years ago and finally pushed forward a bit more. After endless complete freezes of my Win98 machine I gave up. AAAAAHHHHHH boom . I remember liking this a lot more: it’s…. bland? Dread Delusion —A weird looking game that I was drawn to the first time I laid eyes on screenshots a few years ago. I remembered it and felt the time was right to crack this one open. It’s one of the best games I’ve played in the last years. I recorded a playthrough log to convince you to drop everything and go play it as well! Lucy Dreaming —A lovely classic nineties adventure game that’s perhaps playing it too safe to try to be an homage to Monkey et al. ? I still enjoyed myself but the abrupt ending was a bit of a letdown. Speaking of The Aching , the developer explains their philosophy behind the Gorgon Engine . Interestingly, Gorgon is designed to be small and able to run on older original hardware, while new adventure games that look and feel old like The Telwynium are made with PowerQuest for Unity and require hundreds of megabytes. Nobody really cares, but I do. This ACM paper on a conceptual model for ownership types in Rust sheds new light on how the borrow checker works from an educational point of view. More Rust-y stuff—even though I have yet to touch the language—Michael Neumann investigated how long it takes to compile Rust from source compared to other languages. Hint: looooooonnnnggg. As in lonngggggggggg. James also printed his blog in book form years before I did! He selected all coffee-related articles to create a lovely personal caffeinated hardcover. Games That I Missed documents progress on their pinball machine projects . That old electronic stuff inside the machines is mesmerising. Phil Gyford laboriously kept track of how much money he spent each year on music for the past 30 years (via ) In a timely manner, Miss Booleana wrote about Claire Dederer’s Monsters: What Do We Do With Great Art By Bad People? . I asked myself the same question recently and added the book to my toread list. Andrew Webster publishes a Great Truth on The Verge: The Nintendo DS is still the best gaming handheld for travel . Yup. Another paper that confirms LLM-driven gender bias in citations in academic work . James Pennebaker confirms what I’ve been thinking and feeling: expressive writing can influence thoughts, feelings, and behaviours . The link is a past event but a good starting point to find publications by Pennebaker. Chris Kirk-Nielsen begs us to start playing indie games . Stop that Assassin’s Creed nonsense: scroll up and watch the Dread Delusion dev diary instead! Nic tringali sometimes feels the creative drudgery . A surprise ending is in it for you if you decide to read it. Jeff Gerstmann finally decided to apply Rigorous Science (TM) to compile an exhaustive (!!) list of the best NES games ever released in USA . Number one is NOT Mario nor Zelda! I particularly enjoyed Erik Hane’s piece in Typebar Magazine on fandom strain and the IP illness killing Magic: the Gathering . The magazine really is “An interesting thing to read on the internet”, as their footer claims. In a post called Cultures of making and relating , Konrad Hinsen brings the recent Cultures of Programming book our attention. It’s been an open browser tab ever since. Memray looks like an interesting memory profiler for Python, if I ever would need one. GentleOS is a friendly hobby OS for 32-bit PCs. The Corporate EU Observatory revealed that Big Tech invested almost 50% more in lobbying throwaway money ( !) compared to 2020. Diablo II has a new class: the Warlock . I really wish it was playable without the remaster though. Warp Point is a curated list of indie video game websites and Jefklak’s Codex is in it.

0 views

Summary of reading: April - June 2026

"The Nuremberg Trial" by John Tusa and Ann Tusa - a detailed, meticulously researched account of the Nuremberg Trials. There's not a whole lot of side questing in this book - it's all focused on the trials themselves. Interesting read overall, though somewhat dry and academic. "Things Become Other Things: A Walking Memoir" by Craig Mod - a kind of travelogue of the author walking across Japan's Kii peninsula, mixed with his childhood memories and impressions of life in Japan in general. It's a good book, though I thought I'd find more details about Japan here, whereas it's a much more introspective work about the author himself. "Social Justice Fallacies" by Thomas Sowell - the usual data-driven Sowell fare, using real historical data and statistical analysis to tackle some hot political issues like personal liberties, poverty data and affirmative action. "Focus: The ASML way" by Marc Hijink - I was inspired to read this book about ASML and its EUV technology after watching a fantastic Veritasium video on the topic. The book turned out to be a disappointment, however; I was interested in the technology behind ASML's machines, but the book is 98% focused on the human, political and organizational aspects of the company. If you're interested in the tech, the aforementioned video is a much better use of your time. "Every Living Thing: The Great and Deadly Race to Know All Life" by Jason Roberts - combined biography of Carl Linnaeus and the Comte de Buffon, who were groundbreaking naturalists in the 18th century working to categorize all living thiings. It's a history of the very early days of what was called "natural science", and later evolved into botany, biology, zoology, ecology and related disciplines. The title is hyperbolic, but the book itself is interesting and well written. "Junglekeeper: What It Takes to Change the World" by Paul Rosolie - the author is a conservationist in the Peruvian Amazonia rainforest. This book recounts his adventures on the path to establish Junglekeepers - his organization for preserving the forest and its wildlife. It's a nice read overall, though the writing is overly embellished and tiringly hyperbolic at times. "There Is No Place for Us: Working and Homeless in America" by Brian Goldstone - a poignant account of several families in Atlanta struggling with keeping access to suitable rental housing, circa 2020. It covers the danger zone of having just enough but absolutely no buffer, and how life emergencies affect families. Quite impressive investigative work to be able to produce a book like this; the stories are truly touching. The book does reasonably well to skirt around politics without too much preaching, which is appreciated. "Understanding Software Dynamics" by Richard Sites - from the ground up discussion of software performance analysis with sampling, tracing and understanding the different ways in which CPUs are unable to make progress. A good chunk of the book is dedicated to the author's KUtrace system. I wanted to like this book, but ultimately failed. I found it extremely verbose and tedious to follow, full of walls of text. "A Table for Two" by Amor Towles - a collection of short stories and a Novella. The stories all have some whimsical elements in them, and the writing is very good. That said, this book didn't quite recapture the magic of "A Gentleman in Moscow" for me. "Never Enough" by Jennifer Breheny Wallace - talks about the stress teens are under to excel academically and athletically to improve chances of admission to top universities, and what to do about it. Somewhat similar to "The Price of Privelege" and "Unequal Childhoods", though this one is more popular, in the sense that the author is a journalist, not a scientist, and the book is a collection of anecdata laced with official statistics, rather than experiences from the author's own research. "Thunder Below!" by Eugene B. Fluckey - the story of USS Barb, a submarine in the pacific during the latter part of WWII, written by its commander through 5 different deployments against Japan. Written in an engaging writing style, this book is very informative about how submarine warfare looked back then. I was somewhat shocked at the recklesness (suicidal courage?) of the commander though; he clearly was a very capable captain with a talented team, but surely there was lots of luck involved to be able to survive what he describes. That said, the book was also written 45 years after the events, so it's possible that there's some embellishment involved. "Breakneck: China's Quest to Engineer the Future" by Dan Wang - a very nice book about China's manufacturing superiority in the last few decades, as well as other aspects of its society like the one child policy and the COVID-19 lockdowns. The author contrasts China - "an engineer-driven society" with the USA - "a lawyer-driven society", discussing the effects on industrial capacity, culture and civil rights. Informative and well written. "Good People: A Novel" by Patmeena Sabit - an Afghan refugee family settles in Virginia in the early 2000s; this is a novel / mystery focused on their older children, their assimilation in the USA and what that lead to. Haunting book that will be difficult to get out of one's head, particularly for parents of teenage girls. "The Invention of China" by Bill Hayton - the author's thesis is that much of the national ethos of China - the unity of its peoples, language, territory - was invented about a century ago as part of a political agenda. The book is quite dry and academic, but this is key to its effectiveness, as it relies heavily on historical documents. It mentions a fantastic quote from Mao - "Make the past serve the present" - and I feel like this describes the book's main thesis very well. A fascinating example of the narrative of Orwell's 1948 in real life. "Advanced Hands-on Rust" by Herbert Wolverson - the idea of the book is to help one learn Rust through hands-on projects, by building games that use the Bevy framework. My conclusion is that Bevy is a particularly poor way to learn Rust because it's a massive, opaque and opinionated framework that bends your code to its will and conventions. Actually learning or practicing a language by building these simple games from scratch would be much better, IMO. So if your goal is to practice Bevy, sure, this book isn't too bad; but for Rust, stay away. Other than the Bevy issue, the book is poorly edited, with code samples out of sync with the accompanying code and diffuclt to follow to keep the project buildable. Also, since Bevy changes very quickly, you'll have to stick to the older version the book is using - otherwise things just won't compile. On the brighter side, the book does provide some coverage of Rust tooling that is useful - like benchmarking and creating well-behaved crates. But these topics in themselves are hardly worth a whole book. "Mathematics for Human Flourishing" by Francis Su - a math professor's attempt at defining the effects of doing mathematics on meaning in human life. I wanted to like this book, but unfortnately it's a bit too kumbaya for me. While I appreciate what the author was aiming at here, it just didn't click. "Benjamin Franklin: An American Life" by Walter Isaacson "A Gentleman in Moscow" by Amor Towles

0 views
Cassidy Williams 2 weeks ago

Whitespace in Astro 7.0

There’s some new default whitespace handling in the latest version of Astro! I noticed that when I updated my blog template (this blog! Right here!) to the new Astro 7.0 , a bunch of words and spacing were broken up in weird ways. Turns out, in the brand new Rust compiler, there’s some very specific JSX changes. Before, if you had two elements one after the other, like so: It would render as “Howdy y’all” on the page. But, in version 7.0, it would render as “Howdyy’all” instead, with no space. If you wanted to fix it, you’d have to do: Which is very JSX-y like React and other similar frameworks. It was a bit of an annoying change for me, because my blog template has lines like this in a few components that were now rendering incorrectly: But! There’s a solution here, if you don’t want to edit all of your components and templates (like me). In your , add the following in : You can see it in context in my template here , if you’d like. I hope this is helpful for ya! Here’s the upgrade guide for more details!

0 views
Ankur Sethi 3 weeks ago

Deno Desktop

From the Desktop apps section of the Deno documentation : turns a Deno project (anything from a single TypeScript file to a Next.js app) into a self-contained desktop application. The output is a redistributable binary that bundles your code, the Deno runtime, and a web rendering engine into one bundle per platform. I'm happy to see another attempt at solving the biggest issues with Electron apps (other notable attempts being Tauri , Electrobun , and Neutralinojs ). According to the docs, Deno Desktop is only available in Deno's channel at the moment. So I obviously installed it (version ) and tried running a Hello World example app . On first run, Deno spent a few minutes downloading , then packaged the example into an app bundle weighing 308.8MB. I was curious about that download. A quick Kagi search led me to the homepage for a Rust/C library called laufey , which appears to be the tech underpinning Deno Desktop. Running the app bundle popped open a window that looked like this: This is clearly a work in progress. If somebody who works on Deno is reading this, here's a list of bugs I noticed: Deno uses Chromium as the default webview (via Chromium Embedded Framework ). But you can also use the system webview instead: When I ran that command, it downloaded and produced a much slimmer app bundle at 68.5MB. This is what the window looked like: This version of the app exhibited none of the bugs I noticed in the CEF version, except it doesn't have a title. Deno Desktop also has a backend that skips bundling the webview altogether. I didn't try it, but here's what the docs say: No web engine.  Provides window management, input events, clipboard, and the native API surface, but no webview, no   auto-binding, and no   proxy. Useful for apps that draw their own UI (WebGPU, Skia, custom rendering) or as a foundation for non-web desktop programs. The   backend is selected through the   field in  ; the   flag accepts only   and  . A major difference between Deno Desktop and its competition is how it communicates between the code running in the webview and the code running in the Deno runtime: Bindings are not IPC. The Deno runtime and the rendering backend run as threads / processes inside the same address space (CEF) or coordinated process group (WebView). Calls go through in-process channels, and the backend dispatches them from its run loop. This avoids the cross-process round-trip that socket-based IPC frameworks (Electron's ipcMain / ipcRenderer, Tauri's invoke) impose. Arguments and results are still encoded as they cross the realm boundary, but the transport is in-process: no socket, no cross-process scheduling. In practical terms: bindings are fast enough that you do not need to worry about call frequency for typical app workloads. The docs are light on how they pull this off. I'd love to read more about this. There's a built-in auto-update mechanism, including rollbacks if updates fail: Deno.autoUpdate() polls a release server for new versions, downloads binary-diff patches, applies them to the runtime dylib, and stages the result for the next launch. If the next launch fails, the runtime rolls back to the previous version automatically. Updates ship as small bsdiff patches instead of full binary downloads, with rollback baked into the launcher. The comparison page has this bullet-point under the section titled "What doesn't have yet": Shared CEF runtime across apps.  Every app currently bundles its own CEF copy. A managed shared runtime would drop binary sizes to a few MB per app. On the roadmap. Does this mean all Deno apps on my computer could potentially share a single CEF runtime? If yes, that would mean massive disk space savings. But it's unclear if the developers intend to ship this feature in a future release or if it's just a wishlist item that may or may not see the light of day. Deno Desktop is, of course, heavily under development. Some important features are still missing (platform native file dialogs), and it's not clear if others are on the roadmap or not (mobile support). I'm sure many of the missing features will make their way into the final release, and we'll get a clearer idea of future plans in a release announcement. I have a personal interest in anything that aims to replace Electron, so I'll be keeping an eye out for Deno 2.9. The app window had a dark background by default, even though the demo app didn't contain any styles. Browsers don't default to a dark background unless you explicitly opt in using . Even so, opting into dark mode inverts all the default colors, not just the page background. Something is off here. Running the bundle triggered a macOS permissions dialog for and , both of them asking for notification permissions. The demo app didn't use the notifications API (it didn't even contain any JavaScript), so seeing two permission dialogs felt aggressive. Hitting didn't quit the app. The app always opened on the top left of the screen.

0 views
Armin Ronacher 3 weeks ago

The Coming Loop

I don’t prompt Claude anymore. I have loops running that prompt Claude and figuring out what to do. My job is to write loops. — Boris Cherny Over the last months I have watched more and more people build something on top of coding agents that feels meaningfully different from just using a coding agent. Some of this happens on top of Pi which is cool to see for sure! The pattern is the same everywhere though: work is put into a queue of sorts, a machine picks it up, attempts it, stops, and then some harness decides whether that was actually the end. If not, the harness continues the same session, injects another message, starts a fresh session with modified context, or sends the task to another machine. The task stays alive beyond the point where the model by itself would normally have said: “I am done.” I think about that type of loop more than I want to admit. There is already an agent loop inside every coding agent. The model calls a tool, incorporates the result, calls another tool, reads a file, edits a file, runs tests, and eventually produces some answer. That loop is one we have been quite familiar with for a long time. The other loop is the harness level loop: the loop outside the agent loop. That loop is also not new . We have been doing versions of this since early Claude Code days, but that loop is becoming ever more present in agentic engineering and in recent weeks it has started to dominate the Twitter discourse. My current status is that I have not had much success with this way of working for code I deeply care about which turns out to be quite a lot of code. Part of that is taste and part of it is control. I attempt to set a high bar for what I want code to look like, and I want to understand the code I ship. Under pressure, or in a discussion with another human, I want to be able to explain what the system does without first having to ask a clanker to explain it to me. Now there is obviously a question if this desire to understand the code is one that I will still have a few years from now. For now I have not moved past the point of comprehension being important to me. Given this desire, there is something I lack with my experience of code written without me paying attention, particularly from loops. Present-day models tend to produce code that is too defensive, too complex, too local in its reasoning. They avoid strong invariants. They add fallbacks instead of making bad states impossible. They duplicate code, invent bad abstractions, and paper over unclear design with more machinery. Worse though: I so far see very little progress of this improving. If anything, on that front it feels to me that we might even be making steps in the wrong direction. At least for my taste, present-day hands-off harnesses like Claude Code with ultracode produce worse code than what we were producing last autumn. That’s because Claude Code, with Fable for instance will be working uninterrupted on a problem for thirty minutes or more, when previously the process would have been much more human in the loop. Furthermore it’s well understood that models tend to observe some local failure and add a local defense. Karpathy mentioned how they are “mortally terrified of exceptions”. In systems with important invariants, especially persisted data formats or core infrastructure, the right fix is not “handle every malformed case.” The right fix is to make the malformed case unrepresentable or impossible to write in the first place. Yet even with a lot of manual steering, that type of code does not come out of LLMs naturally, and even if the code comes out naturally like that, they will still attempt to handle now impossible errors. When you take that behavior and you put it behind loops, you tend to amplify it. If each iteration adds another small defense, the system slowly becomes less understandable while appearing more robust. The more hands-off you are, the more that happens. It also teaches really bad practices when tools like this are given to juniors without clear guidance. Because if you ask them, why they are doing all that, they will convincingly argue their case. At the same time, it would be dishonest to pretend the loop pattern does not work because it already works astonishingly well in some domains. Porting code one of them. There are already impressive examples of large automatic porting efforts, including the reported work around moving parts of Bun from Zig to Rust . I have used it with success myself to port MiniJinja to Go . Performance explorations are another case where this works beautifully. A machine can try experiments, benchmark them, discard failures, and keep searching. Security scanning fits naturally too and so does almost any type of research: asking a system to explore a complex problem space and report back without necessarily committing lasting code. One thing that many of these have in common is that they either do not generate new code, but transform code that already exists, or they produce code that intentionally does not have a long shelf life. They either produce proof of concepts or ideas, surface findings or are more akin to mechnical transformation. I believe that loops that produce artifacts without necessity of longevity or that create some form of clearly verifiable mechnical translation matters more than the general ability of a harness to mechanically measure a goal. Many successful applications of loops use another LLM as a judge or as an orchestrator. The mechnical translation case can be verified with a binary test case, but it can also be judged by an LLM instead! Claude Code, for instance, is increasingly good at creating entire experimental workflows that it will then execute. Sure, the code it produces is slop, but that’s more the fault of the model than the harness not being a good judge on if a step in the workflow resulted in a net improvement or completion. The harness just needs some signal that lets it continue. It does not have to be objective or binary, it just has to be useful enough to drive another iteration. I absolutely love loops already that take the boring parts out of my day to experiment and measure and to give me ideas. On the other hand using that same looping methodology to write lasting code does not yet sit well with me. The metaphor I like to reach for is one of moving from software as a deterministic machine to software as an organism. I became a software engineer in an enviornment that encouraged me to understand the machine. There was always a layer you could peel off to deepen your understanding. Machines that did not exhibit deterministic observable behavior were maybe accepted, but generally seen as not exactly optimal. Software architecture-wise, I saw it as desireable to push further towards more determinism rather than less. Likewise the ability to understand the code has been an undeniable goal. In practice not always possible we still took pride in writing code so that it became possible even for new engineers to navigate complex code bases through clever architecture. On well designed systems there were always engineers that knew where the invariantes lived, which parts were load-bearing and which changes were safe. Ideally all of that was also well documented. Where that understanding was lacking, it was generally regarded as something to improve upon. Obviously that ideal has always been strained. Many software systems, especially very successful ones had periods where engineers on the team were able to keep them clean. Large software systems are not infrequently too big, too dynamic and too dependent on external services to fit into anyone’s head. Even without LLMs we already diagnose distributed systems somewhat like doctors in that we observe symptoms, form hypotheses, “order more tests”, try some remedies, and observe again. Yet with LLMs we’re pushing much further in that direction and much quicker. We use them to write the code and we also use them for diagnosis and remedy. There are plenty of engineers that already live in a world in which the first step after the occurrence of a production issue is followed by having a clanker read logs, propose root causes and proactively put up a patch. The resulting patch is then often picked up by another machine that reviews, sometimes even landing it on main without any human supervision. Obviously that is powerful and I cannot deny that it sounds appealing. But giving in to that idea, particularly with less and less human oversight means accepting that we may no longer understand the whole system in the same way. We treat it, we monitor it, we stabilize it, but we do not necessarily comprehend it. I have no doubts that for some software, that is okay. Not every line of code deserves human authorship and worse code might have been written in the past. But do I want all software to be authored this way? What’s very uncomfortable is that opting out of this fully machine-driven future may not be an option. Security is the clearest example today. Even if you do not use loops to build your software, other people will use loops against your software. Attackers will run machines continuously and even if it’s not attackers, then security researchers will and some of that automated work will throw up dust but also find real issues. And both the signal and the noise will come your way at a volume that makes it almost impossible to deal with unless you yourself throw a machine at the problem. Daniel Stenberg’s post about curl’s summer of bliss is a good example of the pressure maintainers are already under. As far as I know, AI does not play a tremendous role in the core development of curl today. Yet despite all of this, maintainers are overwhelmed by reports, most of which are now AI-generated ones. If attackers and reporters loop, defenders will eventually need to loop too to keep up. Maybe not to write patches directly, maybe just to triage and reproduce and pressure will increase. The same is true competitively as some teams will out-build others through raw speed. Some projects will suddenly move faster because a tiny group figures out how to orchestrate machines effectively. Some startups will do with five people what used to require fifty. Some people might literally put a machine against your product in a loop and ask it to “make it like the other one.” And if their users are happy, does it really matter? Not all software will be equally affected. Some domains will punish sloppiness and demand trust and responsibility, but a lot of software lives in a world where raw speed, quick experimentation, and vast coverage matter enormously. The scariest part to me is that we become dependent on these new machines in new ways. Software has always depended on tools. I remember the time when I had to pay for compilers. These new tools are a flashback to times where creating software came with real costs. But now it’s no longer a one-time payment, it’s a constant dependency. Not just a dependency on a filled wallet, but also a cognitive dependency. If a codebase is produced by loops, reviewed by loops, patched by loops, and kept alive by loops, what happens when you no longer have access to the same class of systems? What happens when some trade restrictions take away access to the most powerful models? What if just the cost becomes unbearable? What if you and your team just lose the last remaining ability to understand the code without using the machine? We may create codebases that are not merely hard to maintain by humans, but that assume machine participation as part of their maintenance model. This is already happening! It’s not happening everywhere, and it might not even be happening in ways that are seen as problematic, but we see more and more of it. People more and more merge code they cannot fully explain. People lose their ability to create issue reports or discuss things in chat, without augmenting or rephrasing their messages with the context provided by a clanker. Too many people increasingly rely on a machine to summarize or contextualize it. More and more do I encounter people who converse with me through the indirection of an LLM. Again, maybe that is not even going to be wrong, but it’s a massive change to how we did things. I have little doubt that this is where things are going but going there will require us to do something about our tooling everywhere, and not just in the coding agents. Just orchestrating more loops won’t be enough. Better visualizations of changes or orchestration or agents will not restore our understanding. Either we need to find clever ways to jolt the human back into the loop and make the changes of the loops legible long term, or we need to find better ways to compose these ever more complex systems. This is also where my thinking about the role of Pi is changing. Pi has been cautious, and I think that caution is good. I do not want a future where every interaction turns into an uncontrolled swarm of machines making changes I cannot follow. I would not want Pi to become an unmaintainable mess in an effort to win the race towards software that writes itself and I would not want Pi to promote this type of engineering either. At the same time Pi is a harness and harnesses are at the center of people running these new types of experiments. Task queues for coding tasks, orchestration of agents, subagents, durable sessions will matter more and more. Even those of us who have their reservations and are not blindly embracing loops will have to start doing those experiments. We need to, because we need to understand how to make this future bounded and survivable. As you can read from this post, I’m very uneasy about this future. Not cause of fear, but because of caution given experiences with this technology so far. Adopting the idea of harness loops means that the harness decides when work is finished. In the agent loop, the model eventually says “done” and I review. Even before that, I usually steer along the way. I am involved and I enjoy learning along the way. In the harness operated loop I’m not sure what my role even is. Even the “done” signal loses all meanings and just becomes communicated to yet another machine that judges. My role is reduced to that of a messenger. Today I do not like much of the code that I see from systems built that way and neither do I enjoy interacting with too much of software built with AI assistence. Looping is powerful but it removes responsibility more and more, and it at least today very much encourages us to give in to the machine. And yet I have no doubts that this looping future is going to be our future despite the fact that I presently resent it. I already see astonishingly small teams building at impossible speed and I see codebases turning more and more into obscure and confusing organisms that can only be diagnosed by more machines. Those codebases are simultaniously useful and messy. So I guess I’m coming to terms with that the question is not whether we will loop because clearly we will. Maybe the question is that in a future of loops, how do we don’t abdicate judgment, how we can retain rules of good engineering, how we can ensure that responsible human can continue to supervise, how we need to re-think how we architect code to retain sanity along the way.

0 views
Jack Vanlightly 3 weeks ago

Raise the ambition threshold

“Perfection is finally attained not when there is no longer anything to add, but when there is no longer anything to take away.” — Antoine de Saint-Exupéry AI gives us an unprecedented ability to add. The danger is that we begin to mistake accumulation for value. Every new system and feature adds obligations: it must be operated, secured, monitored, documented, integrated, upgraded and eventually replaced or retired. Hackers love a juicy target, even if it’s that half-forgotten service that people are unsure whether it’s safe to turn off or not. If we respond to “cheaper” software creation by producing far more software, we may accumulate obligations faster than we acquire the capacity to discharge them. Under the weight of the proliferation of software, the organization starts to sacrifice its ability to build what it will need next to react effectively to changing market conditions and opportunities. This is the dynamic described by catabolic collapse . Catabolic collapse is a theory of societal decline in which a civilization accumulates more infrastructure than it can afford to maintain. Eventually, an increasing share of its available energy and resources is consumed merely preserving what already exists. Maintenance crowds out renewal. The society begins consuming its own capital simply to continue functioning. Think of debt payments taking up ever larger amounts of the national budget, the transport budget overwhelmed by the costs of fixing too many crumbling roads and bridges. If we accept that every organization, even with AI, has a finite capacity to maintain software, then it follows that we should select carefully the software projects we commit to. I can finally work on that feature that didn’t get funded time after time. I’m going to use AI to build it in two days rather than the estimated two weeks. This is a case of lowering the value threshold and it’s a sloppy way to introduce one of the most transformational technologies in human history. You might get lucky this time, it might end up worthwhile, but then you equally might just be adding that extra bell or whistle, meanwhile your competitor is building a revolutionary new product that will blow you out of the water. AI should raise the ambition threshold for software rather than lower the value threshold. Unless you’re in a small, agile start-up, building a highly strategic product still requires a lot of cross-organizational work. Software engineers, researchers, product managers, market research and customer feedback, the list goes on. But forget all that, let’s reward our engineers (generally focused more on technology than business value) for using huge numbers of tokens to build stuff without careful evaluation of the actual ROI of the work. It’s cool that Johnny finally rewrote that backend system in Rust, or rewrote the build system, or finally implemented that feature few customers actually are willing to pay for. But what was added may have done more for increasing the maintenance costs (and reducing the ability to react to future needs) than actually creating value. Prototyping and demos are another slippery slope. Prototyping is an ideal case for AI with its ability to accelerate work. However, if the prototype represents a system that falls into the category of “previously too low-value to justify,” then the prototype is part of the same problem. It seems that in the initial euphoria at the turn of the year at seeing the new power at our fingertips, some conflated faster for cheaper, more for better. The lesson is that we should continue to apply sensible constraints to what we build. Just because we can build it doesn’t mean that we should.  The danger of using AI injudiciously is greater in large organizations, where the average worker is farther away from the customer and the business value. The more disconnected you are from the success and failure of the organization, the easier it is for tokenmaxxing to help you spend time and money on producing a lot of lower value work. Add the slopification of work and some organizations might actually see a net-negative impact. Indiscriminate token usage in the large enterprise is already showing signs of faltering as CTOs question the value of their AI usage mandates. Business is a perpetual contest for advantage. Companies that spend their new AI capabilities trimming costs and burning down backlogs may soon be leapfrogged by competitors using them to attempt what was previously too difficult, risky or ambitious. So if you find that you are finally clearing all those nice-to-haves in the product backlog, ask yourself if your team is being ambitious enough.

0 views
Blargh 1 months ago

Quake demos raytraced again

This is a follow-up to a previous post about raytracing Quake demos . But first, the money shot: And flat shaded and textured videos. Youtube is Very Aggressive™ with its compression, so the quality there is not good. For pixel quality the above images showcase it better. One of my original reasons for creating the quake demo povray files is that it was a good source of data for 3D experiments. POV-Ray is a great raytracer, though entirely CPU (no GPU) and no longer state of the art. POV-Ray has plenty of built in options, but takes forever to render the 30-60fps demos I want to play with. Also POV-Ray is AGPL now, so nope nope nope nope nope. That’s a dead end. We live in interesting times. We could be living in a time when no two people are running the same email client, or music player, or shell. There used to be a barrier to writing these things custom. I know people who wrote their own shell and use it as a daily driver. I wrote my own email client , and use that. There are many people out there, me included, who are perfectly able to write their own shell, but don’t. For the shell, my need is not above my threshold of putting in the effort. But now? I could, if I ran into more annoyances with Bash. But do you not like Bash ? Just ask the AI to write one exactly the way you want it. If it breaks, well you’re the only user and your fingers are trusted input. “It should be fine” (famous last words) If the static site generator I use for this blog (Jekyll) gives me any trouble, like some Ruby dependency troubleshooting, I’ll replace it with a custom one in a heartbeat. I don’t have to “find” the best renderer, anymore. I can just have AI write a custom one. Oh, but do I have to modify QPov (the demo-to-pov converter) to write a new file format? I could ask AI to add other output format. Or I could have AI write a converter. Nah, I’ll just have AI load the existing POV-Ray files full of includes and macros. Remember, I’m not writing “the perfect general purpose raytracer”. This is not reusable code. I’m just turning my data into raytraced files. I got my initial result in under half an hour. I didn’t save the exact prompts, but they were as short and vague as this: Some notable impressive feats: It’s not povray, but it’s fast. The example 4K frame with antiaalias from earlier took 30 seconds on my laptop (5m39s CPU time). POV-Ray (though admittedly with much more advanced effects) would probably take days . I can now iterate on other things, such as a better way to render the sky and water/lava/slime, and add special effects. I can… but it depends on when I have an itch to continue on this project. Write a raytracer in rust for the files in this directory. The output is a garbage image. Fix it. Make it parallel using the crate. Add optional textures as specified in the input files. I think you got the texture coordinates upside down. Frame 302 has rendering errors. Fix the renderer. Add adaptive antialiasing. It’s a bit slow. Optimize it. Switch to outputting PNG files using the crate. Maximum lossless compression. Add some rendering metadata to the output PNG. It fixed the initial garbage by “realizing” that it could render with POV-Ray, and compare the output. After the initial working version it no longer needed to do that, and didn’t. When the output was no longer garbage it said that it could “see” a hallway now, which made it realize it was done. For the frame 302 rendering bug, it rendered 301 and 303 to compare, and could see a wall disappearing from the rendered output. When working on the problem, it would render an image, and then do image recognition. Yeah, that’s what I’d do too.

0 views
Blargh 1 months ago

AI solving problems

I’ve been able to find some time, lately, to work on my project backlog. And because it’s 2026, I’ve been using AI as a diligent intern. I’ve ranted before about seccomp , but still used it for a project or two. But then, rarely, it triggered an unexpected . That’s exactly the kind of I do want to detect and kill the binary for, so I don’t just want to allow it. I want to know where it’s coming from. showed it’s trying to read . It’s certainly not my code. But just the Rust transitive dependency tree is quite a few crates: Step 1 was to run it in , and reproduce the problem. But it’s a bit trickier than that, because seccomp fully kills the process, so no backtrace. And setting breakpoints requires a few more syscalls to work, just for the process to work under gdb (e.g. ). And turns out some calls fail with if running under a debugger. Yes, I can fix all these things. But why not put the AI intern on it? I’ve previously vibe coded a raytracer just with the prompt “make a raytracer for the files in this directory”. The files in question were my POV-Ray files converted from old Quake demos. It worked very well that time ( flat shaded and textured ), so why not? (youtube’s compression is Very Aggressive™. The originals look crisp) Skipping to the end, yeah: This is now an error instead of killing the binary. Sure. But it reminds me of the well publicised Google interview question “how many piano tuners are there in New York city?”. The best answer I’ve heard is “is it important for the company? — Yes — Then I’d look it up”. That’s the right answer, of course. There’s another “more right” answer to the “why are manhole covers round?” question . Is it important to know which of my transitive dependencies try to read this file? Not really. I could blanket allow that file to be read. I know what that file is, and I actively don’t care if an attacker can read it. So without AI I would not have spent more than 30 seconds trying to find out. And I spent a couple of seconds doing , only showing references in jemalloc, which this project doesn’t transitively depend on. So it’s likely somewhere in some C code. Or in the Rust standard library. So instead I spent 9 minutes reading up on the QUIC protocol for a couple of my other projects, while the AI went brrr. Sometimes the code tries to read readonly and crashes because seccomp. Why does this happen? Sounds good. Change it to do that. diff –git a/src/privs.rs b/src/privs.rs index 19b6307..cce5ef6 100644 — a/src/privs.rs +++ b/src/privs.rs @@ -1,6 +1,6 @@ use anyhow::{Context, Result, anyhow}; use caps::CapSet; -use libseccomp::{ScmpAction, ScmpFilterContext, ScmpSyscall}; +use libseccomp::{ScmpAction, ScmpArgCompare, ScmpCompareOp, ScmpFilterContext, ScmpSys call}; use tracing::{info, trace, warn}; /// Drop privileges to bare minimum. @@ -127,18 +127,32 @@ fn drop_caps() -> Result<()> { ] { f.add_rule(ScmpAction::Allow, ScmpSyscall::from_name(name)?)?; } if with_rustls { // Rustls does some memory allocation. So, now it works. But I’d want to adjust it a bit, to not merely return for any probing of files such as or TLS certificates. I want that to terminate the binary. For merely denying filesystem access on a per path prefix basis I use landlock, but that doesn’t kill the binary. I’m not aware of a way for seccomp to block specific paths (again, seccomp sucks), but maybe the AI knows? But oh no: So maybe it’s time to go outside in the nice weather? “Touch grass”, as the kids say. // glibc malloc may lazily probe /proc/sys/vm/overcommit_memory. f.add_rule_conditional( ScmpAction::Errno(libc::EACCES), ScmpSyscall::from_name(“openat”)?, &[ScmpArgCompare::new( ScmpCompareOp::MaskedEqual(libc::O_ACCMODE as u64), libc::O_RDONLY as u64, )?; if with_rustls { // Rustls does some memory allocation.

0 views
Corrode 1 months ago

Rust Prevents Data Races, Not Race Conditions

Safe Rust eliminates all data races. What it does not do is prevent race conditions in the broader sense: deadlocks, livelocks, and logic bugs in your synchronization. What’s the difference? These two terms get used interchangeably all the time, even by experienced developers, so it’s worth writing down exactly what Rust promises and what it does not. To quote the Rustonomicon : Safe Rust guarantees an absence of data races, which are defined as: All three conditions have to hold at once. If every access is a read, there’s no data race. If the accesses are synchronized (say, behind a lock), there’s no data race. A data race is specifically unsynchronized concurrent access where at least one side writes. This matters because a data race is Undefined Behavior ! A data race does not mean you might read a “stale” value. It means the compiler is allowed to do anything like tear a write in half and reorder it. And you can’t wave this away as a harmless race that happens to work out. As Raph Levien notes in With undefined behavior, anything is possible : It used to be thought that data races could be classified into “benign” and dangerous categories, but research strongly suggests that the former category doesn’t exist. In other words, every data race is a real bug! And because it’s Undefined Behavior, the symptom can show up far away from the cause and much later, in the form of a corrupted value, a crash, or a security hole that only appears under heavy load. For example, here are two threads incrementing the same counter: In many languages, the equivalent compiles and runs, and two threads writing to at the same time can corrupt it. The result depends on timing, so the bug may not show up until the code runs under load. In Rust, it doesn’t compile at all: The borrow checker stops you before the program can exist. Two threads both want a mutable reference to , and Rust’s core rule is that you can never have two mutable references to the same data at the same time. The data race is impossible because the aliasing it requires is impossible. This is the point the Nomicon makes: Data races are prevented mostly through Rust’s ownership system alone: it’s impossible to alias a mutable reference, so it’s impossible to perform a data race. Key takeaways So how do you increment a counter from two threads correctly? You make the access synchronized, which removes the third condition from the data race definition. Wrap the value in a , which lets only one thread touch it at a time: This compiles, and it always prints . The compiler enforces this through two marker traits, and . Roughly: means a value can be moved to another thread, and means it can be shared between threads by reference. A plain can’t be mutated through a shared reference, and a mutable reference can’t be copied across threads. To share and mutate it, you need a type that provides interior mutability while remaining thread-safe ( ), which is exactly what does. Try to share something that isn’t , like an or a , and you get a compile error. (Here the threads can’t outlive , so they borrow it directly. If they needed to outlive the scope, say with , you’d wrap it in an to share ownership: is the workhorse for that.) That’s the whole idea. Rust pushes many concurrency-safety checks from runtime into the type system. Key takeaways So far we’ve made data races impossible. But a data race is only one kind of concurrency bug. The broader category is a race condition : any bug where the result depends on the timing or interleaving of threads. Rust does not protect you from those. In the following example, the code moves money out of a shared bank account. That sounds quite scary, but we make sure to lock the on every access, so there is no data race anywhere in it. One possible output is: but the output varies per run. Both threads locked the mutex and checked the balance before, so how is that final balance negative? There’s a subtle issue: both threads correctly locked the mutex, but they released the lock before they acted on the result of the check. The threads didn’t hold the lock for the entire time. So both threads can check the balance interleaved, seeing $100 before either thread has actually executed the withdrawal, leading both to decide they are cleared to proceed. Then both went ahead and withdrew. The account went negative. Every individual access was synchronized, so the borrow checker is perfectly happy. The bug is that the check and the act are two separate critical sections. Between them, the world can change. This is a race condition (specifically a TOCTOU , time-of-check-to-time-of-use bug), and no type system can catch it for you, because the correctness depends on what you intended the locking to mean . Once you understand this, the fix is simply to make the check and the act one atomic operation, holding the lock across both: You might think that this code is identical to the original, but it’s not. returns a , and here we keep it in the binding instead of dropping it right away. The lock stays held for as long as that guard is alive, which (like any other value in Rust) means until the end of its scope. So the check and the withdrawal now happen inside one critical section, and no other thread can squeeze in between them. When goes out of scope, its implementation releases the lock automatically. In the original code, each produced a temporary guard that was dropped immediately at the end of that statement, so the lock was released the instant each access finished, leaving a gap for a race condition. The compiler can’t know which behavior you wanted. As the Nomicon puts it: It is considered “safe” for Rust to get deadlocked or do something nonsensical with incorrect synchronization. Key takeaways If incorrect locking is “safe,” then so is locking that never finishes. The simplest example: lock the same mutex twice on one thread. Rust’s standard is not reentrant, so the second waits for a guard that will never be released. This compiles without a single warning. Running it: It prints the first line and then waits indefinitely. The borrow checker has nothing to say, because nothing here is unsafe in the memory sense. A deadlocked program isn’t reading bad memory; it’s just not making progress. Why isn’t reentrant in the first place? A reentrant mutex would let you lock it again while you already hold it. The trouble is that Rust’s hands you a to the protected data. If re-locking were allowed, you could call a second time and get a second to the same value while the first is still live, which is exactly the aliasing the borrow checker exists to prevent. So a reentrant mutex in Rust can only safely hand out a shared , not . That’s much less useful, since you usually want a precisely to mutate the value inside. (There might also be a historical reason: ’s started life as a thin wrapper over OS primitives, and some of those aren’t reentrant either.) If you actually need reentrancy, provides it, and it gives out only. You pair it with or for the actual mutation. See this forum thread for more info. Real deadlocks are usually subtler than this. The textbook version is two threads that grab two locks in opposite orders, each waiting on the lock the other holds. But the general problem is that liveness (the program keeps making progress) is not something Rust’s safety guarantees cover. Safety is about not doing the wrong thing; it says nothing about eventually doing the right thing. Key takeaways You might think the bank-account bug was really about : drop the lock, reach for lock-free atomics, and the problem goes away. It doesn’t. The check-then-act trap has nothing to do with locks. It’s about composing operations, and atomics compose just as badly. Atomics are synchronized by definition, so each individual operation is data-race-free. But “each operation is atomic” is not the same as “my sequence of operations is atomic”, which is exactly the gap we just saw with the mutex. Here four threads each do 100,000 increments, but the increment is split into a separate and : Two example runs with two different (wrong) answers: Every and every was a properly synchronized atomic operation. No data race occurred. But two threads can both the same value, both add one, and both it back, and one of the increments vanishes. It’s the bank account again: the gap this time sits between two atomic operations instead of between two locked sections. This is a lost update , which is, once again, a race condition. 1 Notice that we’re using , the strongest memory ordering Rust provides. The bug still occurs because the problem isn’t memory ordering; it’s that the increment is split into two separate operations. The fix is to collapse the two steps into a single indivisible operation. With a lock, that meant holding the guard across both. With atomics, it means a single read-modify-write operation, , which does the load-add-store in one step: With that one change, the program prints every time. This is the same check-then-act trap as the bank account, with no lock in sight; the problem was never about . Key takeaways Safe Rust eliminates data races by design. A program with a data race does not compile. It’s a stronger guarantee than what runtime detectors like Go’s or C/C++’s ThreadSanitizer give you, because those only catch races that actually execute during a test run. Safe Rust does not prevent race conditions in general. Deadlocks, livelocks, lost updates, and check-then-act bugs all compile cleanly and can still produce wrong answers or hang. 2 Geo-ant, writing up a comparison of common C++ bugs against Rust , sums up the whole distinction in one line: Rust does prevent data races and on the other hand you can still deadlock all you want. The reason this distinction matters, and not just pedantically, is that it tells you where to spend your attention. You can stop worrying about torn reads and forgotten locks corrupting memory; the compiler has that. What’s left is the hard part of concurrency: making sure your critical sections cover your invariants, that your lock ordering is consistent, and that your logical operations are as atomic as you think they are. Rust holds an enormous amount for you, and what remains is the part that lives in your intent , which no type system can read. If you want to go deeper on the concurrency side of this, read Rust Atomics and Locks by Mara Bos. It’s free online. Want to get concurrency right in your Rust codebase? I offer Rust consulting, from code reviews and audits to training your team on the patterns the compiler won’t enforce for you, including the concurrency traps in this post. Get in touch to learn more. Fun fact: the count indicates how many increments were lost,i.e., the total number of individual increments that vanished because threads interleaved, read a stale value, and overwrote each other’s progress. So in the first run, 94,648 increments were lost, and in the second run, 231,418 were lost; that’s a percentage of 23.66% and 57.85%, respectively, which is a huge difference just from the timing of how the threads interleaved. ↩ In the context of this article, I treat “data race” and “race condition” as two separate things, which is a useful simplification but not the full picture. The two concepts overlap heavily (many race conditions are caused by data races), yet neither is contained in the other: you can have a race condition with no data race (the bank-balance example above locks every access correctly and still loses money). Under some definitions, you can even construct examples where a data race exists but no observable program behavior depends on it (two threads racing to set an “account was touched” flag that nothing depends on). I recommend reading John Regehr post titled Race Condition vs. Data Race . ↩ two or more threads concurrently accessing a location of memory one or more of them is a write one or more of them is unsynchronized A data race is a specific thing: concurrent access, at least one write, no synchronization. All three at once. A data race is Undefined Behavior, not just a wrong answer. In purely safe Rust, data races are impossible , because they require aliasing a mutable reference, which the borrow checker forbids. Synchronized access is not a data race, so it’s allowed. A is the standard way to share mutable state across threads (an when threads outlive their spawning scope). The and traits are how the compiler decides what’s safe to move or share between threads. Non-thread-safe types won’t compile in a multi-threaded context. A race condition is a logic bug where the outcome depends on timing or thread interleaving. You can have a race condition with zero data races. The withdrawal code locks correctly everywhere and still corrupts its own state. Holding a lock per-access is not enough. The critical section has to cover the whole logical operation , or the invariant can break in the gap. A deadlock is a race condition where threads wait on each other (or themselves) forever. is not reentrant. Locking it twice on the same thread deadlocks. Rust guarantees memory safety, not liveness. A program that hangs is still a “safe” program as far as the compiler is concerned. Atomicity has a scope . The hardware guarantees the individual operation is atomic; making your logical operation atomic is still your job. Atomic operations are individually data-race-free, but composing several of them is not automatically atomic. then is two operations, and another thread can slip in between them. The fix mirrors the lock case: make the whole logical operation indivisible. Reach for and friends instead of a separate load and store. Fun fact: the count indicates how many increments were lost,i.e., the total number of individual increments that vanished because threads interleaved, read a stale value, and overwrote each other’s progress. So in the first run, 94,648 increments were lost, and in the second run, 231,418 were lost; that’s a percentage of 23.66% and 57.85%, respectively, which is a huge difference just from the timing of how the threads interleaved. ↩ In the context of this article, I treat “data race” and “race condition” as two separate things, which is a useful simplification but not the full picture. The two concepts overlap heavily (many race conditions are caused by data races), yet neither is contained in the other: you can have a race condition with no data race (the bank-balance example above locks every access correctly and still loses money). Under some definitions, you can even construct examples where a data race exists but no observable program behavior depends on it (two threads racing to set an “account was touched” flag that nothing depends on). I recommend reading John Regehr post titled Race Condition vs. Data Race . ↩

0 views
baby steps 1 months ago

Only Bounds

bounds are going to be the most impactful change to Rust that you’ve never heard of. They are currently being designed and developed by the Arm team (David Wood, Rémy Rakic, et al.) as part of the Sized Hierarchy and Scalable Vector Extension project goal. This post explores the feature and aims to answer a particular question about the design (the scope of bounds, I’ll explain). But before I dive in, I want to give a bit of context. In today’s Rust, every type parameter (except for ) has a default bound called : A type implements if the compiler can compute the size of a value at compilation time. This is true for almost every type, with a few notable exceptions. Consider , which refers to “some number of instances”. We know that a single is 4 bytes, but without knowing how many there are, you can’t know the size of . This means you can’t have a value of type on the stack (how big should the stack frame be?). However, if you have a function like , that just takes the value by reference (i.e., by pointer), you shouldn’t need to know how big the value is, because you’re not manipulating it directly. You can have a type parameter that doesn’t require , but you have to explicitly “opt out” from the default bound: As a fun bit of historical trivia, this system was introduced way back in 2014 to accommodate Dynamically Sized Types . Before that, was actually a built-in, indivisible type; we even wrote it like for a time. 1 The vs design has served us reasonably well but it is also showing its limits. It turns out that “value has a statically computable size” vs “each value has a distinct size computable at runtime” doesn’t cover all the things you might want. For example, types are types whose values have no known size, even at runtime. And then Arm’s Scalable Vector Extension want to describe SIMD types where every value of the type has the same size (unlike and , where each value can have a different length) but where that size is not known until runtime. Rather than just or , what we really want is to have a richer hierarchy. The current plans look something like this: Two caveats: But now we have a kind of problem. The notation was predicated 3 on the idea that users should specify the default bound they are opting out of – i.e., the is meant to say “I don’t know if this is or not” (unlike the default, where you know it is ). But “opting out” from a bound doesn’t work so well with a multi-level hierarchy. When you write , does that correspond to (but not )? And what if we want to insert another level in between and later? Then we either have to change what means (to refer to the new bound) or we have to have drop two levels down the hierarchy. Even more annoying, what do we do while that middle rung is unstable? Surely shouldn’t refer to an unstable trait… what if we decide to remove it The new proposal is to write or instead of . An bound combines two things: The name comes from the fact that implies . So the default of already means that for free; but when you write only MetadataSized, you are saying “I don’t need the full hierarchy, just will do”. A nice feature of bounds is that they work more like a regular bound. Whereas a bound is saying “I don’t need this”, an bound is saying what you do need. So e.g. if you are writing a function that just has references to values of type does not care what their size is, you can write If you are writing a function that does need to compute the size of values of type , you can ask for that capability: A nice feature of bounds is that, later on, we can add new levels to the hierarchy, and they work normally. For example, suppose we wish to add something like where the size is not known at compilation time but the alignment is . We could change the hierarchy to and functions with (like ) and with (with ) would continue to have the same requirements. But new functions could be written with that would use the new bound. And there is no conflict with stabilization; code that writes can be considered unstable until that middle hierarchy is finalized. Like any other bound, bounds are combined with other bounds to form the overall requirements. So it is possible to write e.g. . This is equivalent to and therefore equivalent to the default and therefore kind of pointless, but you can write it. Similarly, given that , if you write , that is kind of pointless too: you might as well write , which would be equivalent. We plan to have a warn-by-default lint for that. The final strength of bounds is that they allow us to introduce whole new families of default bounds. One example is the idea of introducing a bound . Note that this is a distinct feature and is not covered under the current RFC . All types in Rust today are “movable” and “forgettable”, meaning that you can memcpy the value from place to place so long as you stop using the previous location and you can recycle the memory where it is stored without running the value’s destructor. There is one notable exception – when you pin a value, you it can no longer be moved, and you must run its destructor before its memory is reused – but otherwise this is a hard-and-fast rule. And that’s annoying! The problem is that not being able to guarantee that a destructor runs blocks a lot of unsafe code patterns. For example, scoped tasks a la depend on a destructor for safety . In sync code, this works because we’ve decided it’s UB to unwind a stack frame without running the destructors of values stored there, and so if you put a local variable on the stack, you can be sure its destructor will run. But that doesn’t work in code! And there are times when unwinding without running destructors would be nice. The solution is to introduce a second family of default traits. Unlike the family we saw before, this family defines fine-grained capabilities about how values of that type can be used: The meaning of the traits are as follows: This introduces new checks into the compiler: Some implications: The spur for writing this blog post was a question in a lang team meeting on how bounds ought to work given the existence of multiple “families” of default traits, as I described above. Although the current RFC is looking only at the traits, we expect to look at the “access family” in a future RFC, so we want to be sure we are not making any decisions that won’t scale to cover both. The way I imagine it working is like this. Each default traits is associated with one or more “families”. When you have an only bound, it “opts out” from all default traits in each family that the trait is associated with: You may also want to “opt back in” to some defaults. For example, is a sensible thing to do. It means values that can be moved and destructed but not leaked or forgotten. is an example of a function that only needs . You need to be able to destructure (which moves the optional value out into a local variable and then invoke the closure , which again moves the wrapped value : One interesting thing is the result type . Using only the stuff I wrote in this blog post, it needs to be , because the result will be moved into the value and so forth. But in-place-init would allow for this definition to omit the bound because we could statically guarantee that the will be constructed in place and never moved after that. The method on returns if it is and otherwise returns . This is an interesting one because the value may not be used and therefore requires bounds. The type is an example where we would want to relax bounds from both families: I believe the proper minimum bounds for are: The post may be a bit confusing here. The current RFC is looking only at the proposed “Sized” traits. The family is a speculative future extension that we are exploring but at a much earlier stage. In the beginning, the plan would be that can only be used for well-known, default traits (e.g., , , etc). In the future though there are some thoughts to generalizing it. An alternative that was proposed is to have the opt-out be per-type-parameter. So you might write something like which would “opt out” from all defaulted bounds. Obviously we’d have to bikeshed the syntax, but ignore that for now. The question is whether opting out of all defaults is better than opting out of a single family. I prefer the per-family option for two reasons: Because the , , and similar traits mostly apply to owned values. The examples we saw with were quite typical. And when you are moving values of type around, you need that to be . Yes, that’s true, and I think that particular combo will be common. I don’t think that’s an argument for the approach on its own, though, particularly since that case would not be much cleaner or shorter… …what I think that argues for is actually trait aliases and shorthands . Yes! I think that a future RFC could extend only bounds to allow you to define trait aliases with “only bounds” as supertraits: You could then use an bound to define : Without the , would just be a regular trait bound and would not opt-out from any defaults. Yes, we could! You could define an alias like : Since and are both implemented for all types, this effectively becomes part of both families: Then you can do and opt out from both families at once. Ay, there’s the rub. If we wish to add a new family in the future, let’s say for values that don’t live in the same memory space ( …?), then would be “out of date” because code written against would still be assuming uni-memory-space values. But we could make into an edition-dependent alias or something like that, as has been discussed. Yes! We can introduce a root trait at any time. So we can add the -ness family first, then the family, and then see how we feel. Maybe we find people are very commonly opting out of both– in which case, some aliases are useful, or perhaps a variant. The only way we might “regret” it is if, in practice, people usually just opted out of both and then opted back in to what they want specifically. But we already know that will be common and clearly is more awkward in that case, so I don’t consider that very likely. That name comes from the RFC. There are a few reasons to move away from . The first is that it is possible to have a destructor even if you don’t implement : really refers to user-provided logic in the destructor, but the compiler adds its own logic (“drop glue”, it’s sometimes called) to drop all the fields in the value. The second reason is that the trait itself needs some revision, so moving away from that name lets us have other ways to specify custom logic (e.g., pinned self, or by-value, etc etc). Quite beautifully! In fact, the proposal from Arm for SVE is to introduce the idea of being “a type whose size can be computed at compilation time”, which I find quite elegant. Similarly was proposed by the const RFC as a way to say that a value has a constant destructor. My original proposal for introducing linear types had extending . This would mean that the proposal could simply do and not . However, Alice Ryhl and others pointed out that there are immovable types that must nonetheless be destructed, so it doesn’t make sense to combine those. The Project Goal has a lot of details. The latest updates are available on the tracking issue . If you like watching videos, I recommend David Wood’s Rust Nation talk . I want to close with a meta-observation and a big shout-out to the Arm team. I think they are showing how awesome open-source can be. The Arm team’s primary motivation is adding support for Scalable Vector Extension. This helps Rust make full use of Arm processors. This is, in and of itself, a laudable goal, and valuable to Rust: One of Rust’s assets, in my view, is that it gives you access to all the power your processor has to provide, and that should include unique extensions. But rather than add the feature as a kind of special-case extension to Rust, the Arm team is going further and driving a general purpose improvement, one that will unlock a bunch of other features (extern types and, to some extent, guaranteed destructors; guaranteed destructores themselves unlock scoped async threads and better Wasm integration). I love that. In fact, I recall that in one of my blog posts I proposed writing as the way to spell . I kinda wish we had done that just for the sheer wackiness of it ( ).  ↩︎ I prefer names that refer to the operations that can be performed on the values, so e.g. instead of I would prefer , since it means that you can invoke the function on it.  ↩︎ Little logic pun there for you.  ↩︎ means that all values have the same size and that size can be computed knowing only the type. means that values can have different sizes and that size can be computed given the metadata attached to a reference to the value. Examples include or . is implemented for all values and tells you nothing about the value’s size. I’m excluding the way that Arm’s Scalable Vector Extension fit into this, because it’s orthogonal. The trait names aren’t settled. I’m using the names I understand the libs-api team to prefer; they’re not my favorites, but that’s ultimately the team who owns stdlib bikesheds, so I defer to them. 2 Like any bound, it includes a “minimum requirement” – i.e., means that must implement at least . It additionally disables some default bounds – i.e., we will not add the default bound. , the default, says that you can recycle the memory for a value without running its destructor. says that you can skip running a destructor for a value, but only if you never reuse the memory where the value resides. says that if you have a value of this type, you can reuse the memory where it resides by running its destructor. , which already exists, says that you can memcpy the place and keep using the original place; it’s not really a default, but I included it because it is relevant. , another default, says that you can memcpy the value to a new place if you stop using the original. is the root of this family. It indicates a value that can be “accessed in place” (basically, any value at all). When you move a value (i.e., where is not used later), we will check that the type implements (whereas today, it is always allowed). When you exit a scope, we will check that the values in each local variables have either been moved or have a type that implements . If your function owns a value of type , then you must destruct it before your function returns. You can’t move it (because you don’t know if it implements ) and you can’t leak or forget it either. If your function owns a value of type , then the only thing you can do with it is move it somewhere else. You can’t drop it (because you don’t know if it implements ). No function can own a value of type , because you wouldn’t be able to move it nor drop it, and hence you could not return. But you could have such a value (say) in a . opts out from , , – but not . opts out from , , and – but not . opts out from – but not or . opts out from – but not or . because while it can store or things, it doesn’t have to, it can also store things of an non-computable size (although it does raise the question of how they would be freed, but that’s an allocator concern). because values can form cycles and thus we can’t ever guarantee the destructor will be run. Interestingly, can implement even its contents don’t. First, things like demonstrate that you might very reasonably which to opt out from a single family but retain the default bound. I think it’s likely that there will be many functions that want to opt out of or but not both . You might think that we could make to get the same effect, but I think that would be a mistake. The fact that a value’s size must be computed dynamically doesn’t inherently mean it can’t be moved. Second, it makes it harder to introduce new families later, if we decide there are other orthogonal properties of values that we’d like to relax. In fact, I recall that in one of my blog posts I proposed writing as the way to spell . I kinda wish we had done that just for the sheer wackiness of it ( ).  ↩︎ I prefer names that refer to the operations that can be performed on the values, so e.g. instead of I would prefer , since it means that you can invoke the function on it.  ↩︎ Little logic pun there for you.  ↩︎

0 views
Lalit Maganti 1 months ago

17 bugs in 10 weeks from AI security scanning

Over the last several weeks, I’ve been receiving more security bug reports for Perfetto’s trace processor than I ever have before, all of them found by AI. And I’m very happy about it! These are bugs that would almost certainly not have been found a year ago and it feels good to close these loopholes even though trace processor is by no means security critical. For years, security researchers concentrated their time on the highest-stakes targets: kernels, cryptography libraries, password managers. But there’s a lot of code out there which is security-relevant but not truly security-critical. In my experience, these sorts of projects didn’t draw much attention. Now systems in the long tail can get that attention which they wouldn’t have before. Trace processor is a project which sits squarely in that long tail. It’s a C++ library (yes, Rust would be the obvious choice today but it’s not practical to rewrite, see footnote 1 ) for processing recorded traces of various formats. These are typically traces you collected yourself or in your test infra and process offline so “untrusted input” isn’t much of a concern.

0 views
The Jolly Teapot 1 months ago

Software will never feel the same

When I visit a restaurant, I care a lot about the food I’m being served, and I want everything I order to be delicious. But arguably, my favourite part is understanding and appreciating the decisions behind each item on the menu, as well as the menu design, the choice of cutlery, the lighting, everything. For me, going to a restaurant isn’t only about eating, it isn’t only about having a good time in a nice place: it’s also about appreciating how harmonised everything is, and how the professionals working there have organised their respective skills to make the restaurant as good as it can be. If I learn that a restaurant uses ready meals, and that the people working there simply reheat something they didn’t make and label it as their own because they’ve added some of their own seasoning, it would change my opinion of that place, even if the food served tastes as good as before. But what if I never know? What if I can’t tell if a restaurant I like cheats its way to success? If I’m just tasting what’s on the plate, how can I know? It’s hard to tell. Sure, there are signs. For instance, if the food is served only two or three minutes after I order, or if the dish served is exactly the same as a dish served in another place, these sorts of things. Regardless of the taste, regardless of the food quality itself, I would grow suspicious, disappointed. It would eventually diminish my appreciation of that place, having the feeling that they aren’t really cooking anything themselves. I would simply not go to that restaurant again. If this general feeling of doubt occurs in most places I visit, whether justified or not, it will certainly taint my overall enthusiasm for restaurants. Yes, you’ve guessed it, this is a post about A.I. and software development. As much as I love good software — especially on the Mac — and trying out applications that may end up part of my digital routine, I think what I love the most is appreciating what is seemingly called “the craft”. When I use an app for the first time, the exploration part is my favourite: to see if this app has a chance to stay on my Mac. I try to understand the design and the decisions made building the app the way it is. I actually love spending time digging through the different options and settings. “What are your hobbies, Nicolas? – Well, spending time in preference panels on MacOS surely is in the top 3. – OK, weirdo.” When I manage to grasp the extent of the app’s features, how they can be operated, and how they are laid out in different menus and toolbars, I like to imagine the debates that went on within the teams. I like to see how shortcuts are implemented, I like to find out what changes when I press the Option key. I like to think “ Oh, this is clever ” when something unexpected is available and yet makes perfect sense. If the app is useful to me and fits into my “workflow” , fantastic: I get to appreciate both the craft and the app itself. If the app isn’t really for me, I can still recommend it and appreciate the efforts, the design, the features and the intentions of the team. It’s a sort of catch-and-release approach to software fishing. At least this is what I enjoyed doing until recently; alas, the fishing part now has a strong, inescapable stink; too often with brand new apps something smells… …suspicious. *1 To be clear, there’s nothing wrong with using A.I. in app development, but just like there are good apps and bad apps, there is good and bad use of A.I. I know how A.I. can help some teams to reach their roadmap goals much faster, to fix bugs that were never a priority before, or to build features that couldn’t be afforded in the first place. This is great, and I’m sure this will now be the norm. Objectively, this is a good thing for the Mac. As John Gruber writes on Daring Fireball , it can serve the platform by increasing the number of truly native apps: The Mac has never faced a decline in popularity, but truly native Mac application development (and the skills) did. Now it’s turning around. Mac users are thirsty for Mac apps, and with A.I., they can quench their own thirst and tell the dullards promulgating Electron bundles to pound sand. But not all uses of A.I. are ideally implemented or even well-intentioned. Some apps can appear to be “authentic”, but they are not . Some apps can appear to be native, but they are not. They can appear to be indie apps, with a small dedicated and passionate team, but they are not. Instead, most of them are imagined, created, updated and distributed in a matter of weeks by a single person and their favourite A.I. tool, without any consideration for best practices, security, good UX , or transparency. It’s a free market, and users can decide what’s best for them, sure. Although it’s increasingly hard to tell the difference without any honesty from the developers, and this is where the deception begins. Nowadays, I get a feeling of unease more and more often. I look at a new app’s website, and I feel like I’m being lied to. I feel like I’m in an artisan shop and I see hundreds of miniature Eiffel Towers carved in oak that are way too perfect to be handcrafted by a single person. I’m sad that from now on I may never be as trusting and curious regarding apps as I was before. The good smell of craft is now covered by the stink of doubt and suspicion. When it comes to software, the Olympic Games have been replaced by the Enhanced Games . The sports are the same, and not all athletes take drugs, but it just doesn’t feel the same, does it? Today, not only can we not watch a cute kittie video without wondering if it’s real or not, we now can’t even use an app without wondering if it’s vibe-coded or not. I’ve had this feeling again this week with an app called Aphera . This RAW editing app looks great, native, fast, efficient: exactly my cup of tea. But I have an itch. Something feels too good to be true. Maybe I’m wrong, maybe I’m just paranoid, but when an app seems to come out of nowhere, made by a very small team in a short amount of time, I’m now sceptical. I’ll stick with RAW Power , thank you. Nothing to do with the app Aphera itself. The app could be “legit” all the way, and I may be a fool for not giving it a go. Two years ago, I would have downloaded it already. Today, I have too many doubts; I’ve even grown suspicious of an app I genuinely like: Helium Browser . Some of these A.I.-enhanced apps are fine, I guess, and well-intentioned. Apps like Tolaria are prime examples. Not for me, but I can see the value for some people (and it’s free, so it’s hard to complain here). But most of the time, something feels off. The other day, I received an email from a reader pointing out that the link I used in a post for the great Mac app called MarkEdit was not the one to be expected. The link pointed to the domain where the MarkEdit app I love lived at the time of the post’s publication : . The app developer eventually let that domain expire, and the app has been living only on GitHub since . But when I went to check that link again, the website was indeed for an app called MarkEdit. But it was another app, not the MarkEdit I praise regularly on this blog. This new MarkEdit has since changed its name to AnnexEdit . Not sure if the original naming was a pure coincidence or a sneaky way to capitalise on an existing brand (MarkEdit is a catchy name after all). Maybe anxiety about copyright infringement finally pushed the developer to do the right thing after a few weeks, or maybe it was a simple mistake. This new app, AnnexEdit, is, I believe, another example of AI-generated software. Something feels off: only a few months had passed between the first alpha version and the final product, the app is built using the Rust language (nowadays it’s pretty much a tell), and overall, it lacks personality, it lacks intent, it lacks a human touch. It doesn’t lack a pricing page though, nor a “pro” version. *2 Of course, no mention of A.I.-driven development anywhere on the website. If you’re a serious developer using A.I. to speed things up, I guess you’d be transparent and honest about it , just like a good restaurant would tell its tenants that all the desserts listed on the menu come from a specific patisserie shop. When it’s not even mentioned, yet the app offers some MCP features from the get-go, colour me suspicious. When I look at the feature set of AnnexEdit, it should excite me: it’s a text editor after all. I should be curious to download the app, and give it a go. Who knows, it could probably end up listed in one of my future blog posts about cool apps or whatever. But my suspicions and unease — whether they are deserved or not — are preventing me from doing this. The features are there, but I can’t detect the craft. It seems like software will never feel the same. Now, I wonder: will I ever be able to give a chance to a new app again? I fear that the magic and my software innocence are gone. Stole a joke from Stephen Colbert here.  ^ Question: if AI-generated content cannot be copyrighted , does it mean that an AI-generated app cannot be sold?  ^ Stole a joke from Stephen Colbert here.  ^ Question: if AI-generated content cannot be copyrighted , does it mean that an AI-generated app cannot be sold?  ^

0 views
Giles's blog 1 months ago

Using Safetensors with Flax

I'm porting my PyTorch LLM code to JAX , using Flax as the neural network layer. For various reasons I wanted to use Safetensors to store checkpoints of the model. It took a little while to get it working; here's the trick I learned. If you look at the Safetensors docs, you'll see that it doesn't mention a JAX implementation -- indeed, searching for "safetensors jax" at the time I'm writing this gives you a link to this GitHub repo by Alvaro Bartolome -- which was last updated in 2023. However, if you look more closely at the docs, they do have a link to the Flax API . I feel this is somewhat misnamed, as it is actually a JAX API. There's no reference (again, as of the time of writing) to Flax in the source -- it's all just JAX code. And in fact Bartolome's library uses it under the hood. There is one problem, though. The API works with simple single-level dictionaries, with strings mapping directly to JAX arrays. For example, the function has this signature: This can cause problems if you're not careful. If you look at the Flax documentation on checkpointing , it suggests that you use Orbax 1 , which has its own API and file format, but then goes on to say: When interacting with checkpoint libraries (like Orbax), you may prefer to work with Python built-in container types. In this case, you can use the and API to convert an to and from pure nested dictionaries. I initially put two and two together -- that and the dictionary-based API for Safetensors -- and got five, and tried feeding one of those "pure" dicts into Safetensors. I got a very confusing error: It's worth digging in to why that happens. The problem is that although Safetensors is expecting a dict of strings mapping to tensors, it doesn't check that that is what it actually gets. And while the dictionaries from are "pure", they are also nested (as the docs say!). Even for the simple model I was working with, I got a structure like this: So, we had strings mapping to dicts, and those dicts mapped from strings to the JAX arrays. More complex models would have had deeper dict structures. Now, internally inside Safetensors, the Flax/JAX API is a simple wrapper. It iterates over the keys in the dictionary it's been provided with, and tries to convert their respective values into NumPy arrays. It does that by passing them into NumPy's function, which accepts things like lists, tuples, and NumPy arrays, and converts them into arrays. JAX's own class exposes an interface that it recognises, so they're converted without trouble. Once it's done that, it passes the result to a lower-level Rust implementation that actually converts everything to Safetensors format. But because Safetensors didn't check types, in my case it was iterating over the top level of the dict, trying to convert the values to NumPy arrays, and got something like this: That is -- because it assumed that the values in the top-level dict were JAX s, it blindly tried to convert them to NumPy arrays. But they were dicts (that happened to map from strings to arrays) -- and if you ask to create an array based on a random object, it happily does so and wraps that object in a NumPy array, with a of . When that is then fed into the lower-level Rust code that is trying to write the file, it encounters NumPy arrays that have a it can't handle, -- hence that error: It all makes sense when you read through the code, but I was a bit perplexed for a while! I think all this might be the reason why Bartolome created his GitHub repo. In the README, he says that: There are no plans from HuggingFace to extend safetensors to support anything more than tensors e.g. , see their response at huggingface/safetensors/discussions/138 . So the motivation to create is to easily provide a way to serialize using safetensors as the tensor storage format However, you don't need to use that library to serialise simple Flax models. Consider how PyTorch models get serialised to Safetensors; my LLMs have keys with names like , , and . They're "flat" dictionaries mapping strings to PyTorch Tensors, similar to what Safetensors wants for these Flax ones, but they use dots to separate different levels, with integers for list items and strings for field names. Looking at the pure-dict structure I had for my model: ...you can see that you could walk the dictionary structure to generate keys like and . That would be easy enough to code up. But -- as Adithya Dsilva points out on GitHub -- you can get there even faster by using . That returns a (non-dict) structure like this: If you iterate over that , you get tuples where the first element is that tuple of strings, like , and the second is a object wrapping the JAX . The tuples mirror the dot-separated string format in the PyTorch-style Safetensors files. objects also implement an interface that can understand, so you can quickly and easily convert the to a regular dict for Safetensors: (You need to wrap in a because if you have a in your model, the item in the tuple will get an integer index rather than a string). You can go the other way pretty easily too; given a model, you can load the saved checkpoint into it like this (because accepts raw JAX s in place of explicit s): A little more work than I'd ideally like, but given that it can be tucked away in general / functions, not too big a deal. Hope that's of use for other people coming across this problem! I'm beginning to feel a bit swamped with all of these libraries with names ending in -ax. It reminds me of the names of the characters in Asterix's village ...  ↩ I'm beginning to feel a bit swamped with all of these libraries with names ending in -ax. It reminds me of the names of the characters in Asterix's village ...  ↩

0 views
Corrode 1 months ago

Veo

I don’t know about you, but to me there are few things as interesting as the hardware/software interface: the point where carefully written code meets the messy, physical world of sensors, lenses, and real-time constraints. It’s where a clever abstraction either holds up or falls apart the moment a real signal hits it. That makes Veo a perfect guest. The Copenhagen-based company builds AI-powered cameras that record and analyze sports matches, from grassroots football pitches to professional clubs, and then turn hours of raw footage into something coaches and players can actually use: automatic highlights, player tracking, and match analysis. To get there, they have to capture panoramic video on a custom camera, follow the action without an operator, and crunch an enormous amount of data, reliably and at scale. My guests sit on both sides of that interface. Anders Hellerup Madsen works close to the metal on the camera itself, on the embedded firmware and the GStreamer media pipeline that turns raw sensor data into video. Gorm Casper works further up the stack, on the backend that ingests, processes, and analyzes those matches in Rust. Together we talk about where Rust fits across that whole journey, the trade-offs of doing media and computer vision work in a systems language, and what convinced a sports-tech company to bet on Rust for the parts that absolutely cannot fall over. CodeCrafters helps you become proficient in Rust by building real-world, production-grade projects. Learn hands-on by creating your own shell, HTTP server, Redis, Kafka, Git, SQLite, or DNS service from scratch. Start for free today and enjoy 40% off any paid plan by using this link . Veo (Veo Technologies) is a Danish sports-tech company, headquartered in Copenhagen, that builds AI-powered cameras and a video platform for recording and analyzing matches. Instead of relying on a human camera operator, a Veo camera captures the entire pitch in panoramic video and uses computer vision to automatically follow the ball, generate highlights, and produce analysis that coaches, players, and clubs can use. What started in football has grown into a platform used by tens of thousands of teams across the world, spanning many sports, from amateur clubs to professional organizations. Anders Hellerup Madsen is a Senior Software Engineer at Veo, where he works on embedded firmware and on the GStreamer -based media processing pipeline that runs on the Veo camera. He is also a GStreamer contributor. Gorm Casper is a Software Engineer at Veo. After many years working on the frontend, he now spends his time on the backend, writing Rust. He holds a Master’s in Digital Design & Communication from the IT University of Copenhagen. GStreamer - The open-source multimedia framework at the heart of Veo’s camera pipeline gstreamer-rs - The Rust bindings for GStreamer OpenCV - The open-source computer vision library Nvidia Jetson - Like a Raspberry Pi, but with more video processing capabilities glib - The foundation of gstreamer, also of GTK, Gnome, and many more ffmpeg - An easier video manipulation tool, but without good support for custom pipeline elements CUDA - Nvidia’s tooling to run C++ code on the GPU Sebastian Dröge - Amazing Rust and GStreamer developer OCaml - A really nice language and an inspiration for Rust Rustonomicon - The dark arts of unsafe Rust Latest Announcement from Nvidia - CUDA for Rust - Nvidia’s experimental Rust-to-CUDA compiler, cuda-oxide Rust GPU - Write and run GPU code in Rust, announced on 2026-05-12 Temporal - A durable workflow engine Rust in Production: Astral - The Python company that does uv and ruff, with Rust serde_json::Value - The Rust analogue to Python’s dict ReasonML - OCaml with a better syntax bedquilt - Write 80s Text Adventures with Rust Rust Book: Transfer Data Between Threads with Message Passing - The chapter explaining the Go motto “Do not communicate by sharing memory; instead, share memory by communicating” Veo Website Anders Hellerup Madsen on LinkedIn Anders Hellerup Madsen on GitLab (freedesktop) Gorm Casper’s Website Gorm Casper on LinkedIn Gorm Casper on GitHub

0 views
Dangling Pointers 1 months ago

Yield Not Thy Core

Yield Not Thy Core Achilles Benetopoulos, Peter Alvaro, Andi Quinn, and Robert Soule EUROSYS’26 This paper describes a solution to the placement problem in distributed systems. If you model a computation as a directed graph, how do you optimally distribute the graph among a set of cooperating computers? The authors propose a dynamic placement system and implement it in Magpie . One common solution to the placement problem is to ship data over the network. For example, a set of compute nodes could access data via network requests to a separate set of nodes running Redis servers. At the opposite end of the spectrum, code can be shipped over the network. The canonical example is expressing computation as a SQL query which is sent to the node(s) that hold the relevant data. Magpie proposes a more fluid solution, where both code and data can move dynamically. In Magpie, an object represents data that is operated on. What makes Magpie objects unique is that pointers to data stored in an object are encoded as tuples. This allows Magpie to dynamically move objects around the system without invalidating pointers. The downside of this approach is that it prevents traditional libraries (that rely on raw pointers) from being used in user code. Magpie assumes a high degree of inter-object locality, so any given object is stored by exactly one node (i.e., a single object is never split between multiple nodes). User code is expressed in terms of nanotransactions and epics . A nanotransaction runs to completion on a single node and accesses a pre-specified set of objects. The Magpie runtime ensures that all objects accessed by a given nanotransaction are resident on a single node before executing the nanotransaction. The code for a nanotransaction is simple, because there is no need to query data over the network, and there is no need to deal with locking. If a hazard is present between two nanotransactions, they will execute serially. In Magpie, nanotransactions are written in Rust. An epic is a computation graph where each vertex is a nanotransaction and each edge is a data dependency. In contrast to nanotransactions, a single epic can be distributed across multiple nodes. Magpie schedules nanotransactions once all data dependencies are satisfied. Conflicts between concurrently running epics are handled via snapshot isolation . Any particular epic has a consistent view of each object and may abort in the event of a conflict. Scheduling and data movement are implemented hierarchically. A worker node can locally determine if it has ownership of all dependencies required for a nanotransaction. If this is the case, then the worker node executes the transaction immediately. Otherwise, the worker node uses a local ownership cache to try to determine if another node has all required dependencies and communicates with that node if possible. Failing that, scheduling is performed by a global orchestration node. Fig. 9 compares Magpie to memcached executing a workload that involves a user-specified read-modify-write operation: Source: https://dl.acm.org/doi/10.1145/3767295.3803616 Magpie is able to offer a lower latency because it is able to ship the entire read-modify-write operation to the server that holds the relevant data, rather than requiring multiple roundtrips. Some applications may benefit from being able to indicate that an object is rarely changed and thus can be distributed among multiple nodes at the same time. Thanks for reading Dangling Pointers! Subscribe for free to receive new posts.

0 views