7034 stories
·
166 followers

[$] Stabilizing Rust's never type

1 Comment
By Daroc Alden
September 8, 2026

A function's return type is supposed to indicate the kind of data that it produces. Rust's "never" type, which is denoted by an exclamation mark ("!"), is the type the language uses to mark a function that never returns and other places where a value can never occur. For a long time, the never type was used internally by the compiler, but was considered an unstable feature. On August 24, after more than two years of work, Rust-compiler-contributor "waffle" finally managed to stabilize the type. It took so long, in part, because it involved a small breaking change to previous Rust editions, which the compiler maintainers needed to ensure did not impact much real code.

(Note: Rust also uses exclamation marks to indicate calls to macros. The way the syntax is constructed, a place where it is valid to use the never type is not a valid place to put a macro invocation and vice versa.)

Why a never type?

There are two reasons that Rust has a never type, one practical and one philosophical. The practical reason is that it allows for more efficient generic code. For example, consider the FromStr trait in the standard library, which is used for types that can be instantiated from a string:

    trait FromStr: Sized {
        type Err;
        fn from_str(s: &str) -> Result<Self, Self::Err>;
    }

FromStr::from_str() either returns a converted result, or a custom error type. For example, attempting to convert "foo" into an integer will return a ParseIntError. But some types have an infallible conversion. For example, it is always possible to convert a string into a ByteString. That implementation of FromStr could set Err to be the never type. Then the compiler would know that the error branch of the returned Result is never present, and could optimize out all of the code that touches it or checks for it.

    impl FromStr for ByteString {
        type Err = !;
        fn from_str(s: &str) -> Result<Self, !> { ... }
        // Keeps the same generic interface,
        // but generates code equivalent to:
        // fn from_str(s: &str) -> Self { ... }
    }

The philosophical reason involves correct type inference. In Rust, constructs such as if statements and while loops are expressions; their results can be assigned to a variable. The compiler needs a type to infer for the result of an infinite loop, if the programmer writes one. That shouldn't come up often in real code, but it turns out to simplify type inference to be able to treat that case uniformly, rather than adding special rules to handle it.

In particular, the never type has a useful property for simplifying code: it automatically coerces to any other type. This sounds strange, but it is safe, since the never type represents the "result" of a computation that will never produce a value. So, anywhere that the code claims to have a value of the never type, the compiler knows that it can't possibly reach that code, and therefore it's safe to ignore it. This is a form of type-system-driven dead-code elimination.

For both of these reasons, Rust programmers have wanted to be able to use the never type in stable versions of the language. Making that happen required resolving a particularly thorny corner case.

Never fallback

Because of the way that conversions from the never type to other types are implemented, the compiler can sometimes end up in a situation where it cannot naively infer the concrete type of an expression. Consider this example, which defines an anonymous function (using ||, which is like Python or LISP's lambda) that never returns, and then calls it in a way that expects a concrete error type (using the ? operator):

    let function_that_never_returns = || { loop {} };
    function_that_never_returns()?;

That infinite loop is given a type of ! which is then implicitly converted to whatever the function is supposed to return. But since the function is defined locally and not given an explicit type, the compiler does not have sufficient information to say what that type is. The problem could be fixed by giving the function an explicit return type:

    let function_that_never_returns = || -> Foo { loop {} };

Since such an annotation would only be required in cases where the function cannot return anything, however, it would be a bit pointless to require the programmer to assign a fictitious type to it. So, the compiler includes a special rule: if, after all other type inference has been done, there is still an ambiguous type that cannot be determined, just assume that it should be the designated fallback type. Prior to the 2024 edition of Rust, that fallback type was () (the unit type, which has exactly one possible value). In the 2024 edition, the fallback type was changed to ! itself, essentially canceling out the implicit conversion. In the compiler internals, the never type still gets converted to an unknown type and then falls back, but from the programmer's perspective the behavior is identical to having the never type only undergo implicit conversion when required for the types to make sense.

That change of behavior was, technically, a breaking change. Type inference for some code could change, which could in turn cause compilation errors. That is the purpose of Rust's edition system: allow breaking changes in the front-end design of the language without breaking older code or requiring the whole ecosystem to update at once. In this case, however, there were reasons to want the new behavior backported to old editions.

Never infallible

For many years, the standard library has had an Infallible type to work around the unstable nature of the never type. It served the same semantic purpose as the never type, but did not have any special compiler support. Therefore, code using it would be technically correct but suboptimal (such as having an extra layer of tags in an enumeration or emitting dead code), because the optimizer would not always be able to remove references to Infallible. It was planned that, when the never type was eventually stabilized, Infallible would become a type alias for ! and all that old code would silently become more efficient. However, people pointed out a handful of ways that redefining Infallible had accidentally been made into a breaking change. Since ! has implicit conversions, changing the definition of Infallible could result in existing code needing additional type specifiers in order to type check.

Luckily, changing the definition of Infallible and changing the default fallback type, while both breaking changes, nearly cancel out. Any code that refers to the standard library's Infallible type by name would continue to work; it is only places where type inference is implicitly expected to produce Infallible that pose a risk of breaking existing code. With Rust's lack of implicit conversions in most cases, that will most often come up in places where the never type used to be implicitly converted to Infallible. If Infallible is made to be a type alias of the never type, then those places may experience never-type fallback, which would, in turn, change the inferred type and cause a compilation error if the never fallback type were not updated at the same time.

With both changes occurring simultaneously, the Rust maintainers believed that almost all existing Rust code would continue to compile — but "almost all" is not a reassuring qualifier when dealing with backward-incompatible changes. The Rust community does have a solution to this in the form of crater, which can download and compile all publicly available Rust libraries from crates.io in search of code that is broken by a compiler change.

Never say never

Waffle ran crater in April and found that, while there were 3,300 crates negatively impacted by the change, only seven were fully broken, with the rest broken by depending on old versions of libraries that had since been fixed. In the latter case, the problem would theoretically be fixable by releasing backported fixes for a handful of core libraries. This is not an accident; Rust has been emitting a warning whenever code triggers never-type fallback in a way that will break with the new change since 2024, so most libraries had plenty of time to update of their own initiative. The most common remaining error observed by crater is code that calls a generic function without enough type information for the compiler to pick a specific return type. Consider this function:

    fn foo<T: Default>() -> Result<T, Error> { ... }

It returns either a value of some caller-chosen type T that must implement the Default trait, or an error. If it is called without specifying a value for T, however, then type fallback can kick in:

    // No type specified.
    foo()?;

Previously, this would have made the compiler assume that T should be (), which implements Default, and so the code compiles. After this change (and on the 2024 edition), the compiler assumes that T should be !, which doesn't implement Default, and therefore causes a compilation error. The fix is to explicitly specify the type that foo() should return, either in the call or by pattern-matching assignment.

    foo::<()>()?;
    // or
    () = foo()?;

Even though it's not a complicated change, the Rust maintainers were not willing to break 3,300 crates. Waffle was asked to work with the maintainers of common libraries to backport simple changes like the above (making a new patch version, which many Rust build environments will pick up automatically), in order to reduce the number of libraries depending on broken dependencies. Several library authors were willing to make the backports, but some refused on the grounds that those old versions were past their end of life. Those maintainers pointed out that users could stay on an older version of Rust or update to the maintained version of the library. Even so, the successful backports addressed 1,553 of the failing crates.

After fixing a handful of related problems to reduce the number of broken crates even further, the Rust maintainers eventually agreed that even though there would still be some broken code it was worth making the change to simplify the language. So, starting in Rust 1.99, the never type will be stable and Infallible will be a type alias for the never type. Users who find that this breaks their code have a few options:

  • Stay on Rust version 1.98.
  • Update their dependencies to supported versions that include a fix for the problem.
  • Add a patch to explicitly specify the return types of affected function calls.

On the one hand, this is a breaking change, and people may see code that had remained stable and working suddenly fail to compile. That could be seen as a violation of Rust's commitment to backward compatibility. On the other hand, the problem is relatively rare, there are multiple simple ways to fix it, it has been warned about for years, and it has always been part of the plan for the language. Additionally, the Rust maintainers worked directly with the community to find and address the breakage, even going so far as to help backport fixes to long-dead versions of popular libraries. So, the whole process could also be seen as an affirmation of Rust's commitment to backward compatibility.

In the future, people learning the language will hopefully find the never type just a little less special. Either way, most users of Rust will probably not be affected at all, but never say "never".

Read the whole story
jepler
2 hours ago
reply
Rust devs have convinced themselves that their backward compatibility promise is not worth keeping. It's just a small thing .. this time
Earth, Sol system, Western spiral arm
Share this story
Delete

Soft-launching the DiffOS project

1 Share

Today marks the day of soft-launching of my Debian derivative, which I’ve been using on several of my own machines for the past year or so. This is still work in progress, but I wanted to establish a launch date of the project so below is the DiffOS manifesto as motivation for continued work.

DiffOS is For Freedom! DiffOS is the Debian Increment For Freedom Operating System.

  • Aspire to the goals of GNU FSDG and become a recognized Free GNU/Linux distribution.
  • Uses Debian GNU/Linux as upstream.
  • Support for all architectures supported by Debian.
  • Provide Containers, Cloud Images, LiveCD and installer ISOs.
  • Provide standalone hosting of the package repository.
  • Provide documentation and issue tracker.
  • Keep changes to a minimal, in particular:
    • Upstream-first policy to prefer that any changes are made in Debian, and only if that fails they are considered for DiffOS.
    • Binary package re-use for as much as is possible.
    • Don’t modify any source-level Debian package unless REQUIRED by the FSDG (e.g., for freedom concerns) or REQUIRED by the Debian project (e.g., for branding reasons).
  • Publish a list of packages that are added, removed or modified compared to Debian, with justification for each change.
  • Publish Diffoscope-style outputs comparing our artifacts with comparable Debian artifact.
  • Everything built from CI/CD pipelines, inspired by the Salsa CI pipeline but extended to cover the package repository and installation images as well, to allow modern GitSecDevOps of the entire supply-chain.
  • Use inspiration from other Debian-derived FSDG distributions Trisquel GNU/Linux and PureOS, and broader with GNU Guix especially on how to approach existing freedom concerns in packages.
  • Git Forge agnostic. While currently hosted on GitLab.com, scripts and configuration are (or will be) designed to allow setup on self-hosted GitLab instance, Codeberg.org or self-hosted Forgejo.
  • Maintained by Humans – THE HUMAN MANIFESTO FOR THE AGE OF ARTIFICIAL INTELLIGENCE.

Happy Hacking!

Read the whole story
jepler
4 days ago
reply
Earth, Sol system, Western spiral arm
Share this story
Delete

Fat Tire Brakes Get Wireless Upgrade

1 Comment

At first glance, wireless brakes seem like a recipe for disaster. For something as critical as braking, many bicyclists might prefer a physical connection to their method of safely controlling speed. But there are a number of surprising benefits of electronic or wireless braking systems. For one, they can enable systems like anti-lock braking systems and for another they can eliminate cabling or hydraulics on a bicycle. For these reasons, and just for the thrill of it, [Berm Peak] built a set of wireless brakes for his fat tire bicycle to test out the possibilities.

The system uses a set of ESP32 microcontrollers to handle inputs from the braking lever and outputs to the front and rear brakes, as well as a central control unit and display. The brakes themselves are controlled by actuators from car door locks, which when combined with the springs from the stock calipers work to apply a wide range of braking force to the wheels. These did take a bit of prototyping to get working right, by changing to higher quality calipers, increasing the angle of the actuator, and adding longer levers, but eventually a working braking system started to appear.

But replacing a hydraulic system with an electronic one isn’t where something like this shines. [Berm Peak] was able to add in a number of features impossible in traditional braking systems. Not only does this have an ABS system and the possibility to remotely slow down his children’s bikes when they’re riding, but there’s also a braking equalizer that allows the rider to control how much braking there is at certain positions of the brake lever, and another setting called “derp” which doesn’t engage the brakes at all until a certain threshold has passed. This might end up being the next big trend in mountain biking, unlike airless tires.

Read the whole story
jepler
10 days ago
reply
I'll join the chorus of "no never"s from over here.
Earth, Sol system, Western spiral arm
Share this story
Delete

Your AGENTS.md file doesn’t actually do anything

2 Comments

AI coding bot vendors tell you to use a context file with instructions for the chatbot on how to edit your project. Claude Code wants a CLAUDE.md, or there’s AGENTS.md in general. [Anthropic]

But does your AGENTS.md do anything? A team at ETH Zurich tested AGENTS files. They ran the bots over some test projects with and without AGENTS.md: [arXiv, PDF; presentation, video]

Surprisingly, we find that providing context files does not generally improve task success rates, while increasing inference cost by over 20% on average. This observation holds across different LLMs, coding agents, and for both LLM-generated and developer-committed context files.

A chatbot-generated AGENTS file makes the coding agent succeed at coding tasks about as well as having no AGENTS file — or slightly worse. A human-written AGENTS file gives slightly better success rates, but not much better.

Having an AGENTS.md increases the token cost quite a lot — whether the file is chatbot-generated or human-written. (Anthropic probably considers this a feature.) The bot churns through more stuff, but it’s no more likely to finish your task successfully.

Overviews of the file structure of the code repository are not useful to the agent. They don’t help the bot get on with editing the listed files any faster.

Does the AGENTS file do anything? If you mention particular tools in the AGENTS file, the coding agent will use them more often. That’s about it.

So when Anthropic recommended CLAUDE.md, they clearly didn’t measure if the instruction file actually does anything. They just chucked it in as a magical incantation you can invoke so you can hope the robot gets it right. This time. At least you might feel like you’re doing something.

This is very like how Anthropic writes Claude Code, which is mostly a pile of magical incantations telling the robot not to screw up yet again. Anthropic doesn’t know any other way to steer the bot.

The researchers’ recommendation: keep the instructions short. Be minimal. Or, of course, you could write a nice long file and chug through those tokens!

The closest anyone can come to a use for the AGENTS.md file is so you clarify your own thinking on how the project works, One developer on Reddit said: [Reddit, archive]

The actual token-level context it provides matters less than the fact that writing it forces you to articulate things about your codebase that were previously just in your head.

Yeah, that’s what we used to call writing. Be right back, just starting the project wiki. I call it HUMANS.md.

Read the whole story
jepler
11 days ago
reply
BRB writing the word "DEVELOPERS" repeated 112 times in AGENT.md
Earth, Sol system, Western spiral arm
tante
12 days ago
reply
Researchers at ETH Zurich show that "Agents.md" files don't do much. "AI" is cosplay all the way down.
Berlin/Germany
Share this story
Delete

3D-Printed Skin Gives Robots the Sensation of Touch

1 Comment
Schematic diagram of the touch-sensitive skin. (Credit: Haofeng Chen et al., ArXiv, 2026)
Schematic diagram of the touch-sensitive skin. (Credit: Haofeng Chen et al., ArXiv, 2026)

Hypoesthesia, more commonly referred to as numbness, is one of the more distressing ailments that can affect us humans, primarily because it reminds us of just how much we rely on our sensation of touch in daily life. From experiencing the world around us, handling objects, noticing when you just bumped into that side table again and the comforting hug of a fellow human being, touch is perhaps the most important of our senses.

In that regard the recently published research by [Haofeng Chen] et al. on giving robots a skin that can experience touch seems rather important as it would give especially humanoid robots a more natural way to interact with their environment, using feedback from touch.

Poking the artificial skin. (Credit: Chen et al., arXiv, 2026)
Poking the artificial skin. (Credit: Chen et al., arXiv, 2026)

One of the essential parts of biological skin is that it is teeming with sensors, at a density level that provides excellent resolution as required, down to sensing e.g. small surface imperfections with one’s finger tips. Replicating this with an artificial skin for robotics has always been a problem, due to the wiring and/or reliability nightmare this poses with typical approaches. Instead of focusing on many individual sensors, [Chen] et al. focused on effectively creating the equivalent of a resistive touch screen in skin format.

The basic principle underlying the demonstrated artificial skin is electrical impedance tomography (EIT), which uses surface electrodes to form a tomographic image based on measures electrical resistivity. Core here is the flexible TPU layer with electrodes and the conductive fabric patches attached to the top TPU cover layer. The electrodes continuously measure the resistivity, with disturbances from those patches due to touch events on the cover layer altering these values. From this EIT can be used to reconstruct the location and strength of the touch event.

The results from the created prototypes were promising, with only 16 electrodes sufficing to create a fairly accurate pressure map. Hardware-wise this makes it thus quite uncomplicated, with the characterization of the TPU porosity and such along with the EIT algorithm (provided in the paper) probably being the biggest hurdles for hobbyist recreations.

Read the whole story
jepler
15 days ago
reply
congratulations, you've invented resistive touchscreens. maybe with multitouch, which is cool since resistive touchscreens never supported it?
Earth, Sol system, Western spiral arm
Share this story
Delete

Linux Fu: Improving FTP

1 Comment

FTP isn’t exactly cutting-edge technology. These days, if you control both ends of a connection, you’re probably using scp, SFTP, rsync, or something even fancier. But FTP refuses to die, especially if you are perusing old public FTP servers or talking to retrocomputers. Every now and then, you still need an FTP client. Naturally, there are plenty of graphical clients. But some of us would rather stay at the command line. You could just type ftp, of course. It works, and if you haven’t used it lately, it is probably better than you remember. However, I’ve long been a fan of NcFTP. While some other FTP clients have caught up, it still has unique features that make FTP a lot more productive.

Not Your Father’s FTP

Before maligning the standard ftp command, though, we should point out that it probably isn’t the FTP client you remember from 30 years ago. For example, on openSUSE Tumbleweed, /usr/bin/ftp is really tnftp, a portable version of NetBSD’s enhanced FTP client. Debian uses it too; the ftp package in both Bookworm and Trixie leads you to tnftp. Since current Raspberry Pi OS is based on Debian Trixie, you’ll encounter tnftp there, too. That’s significant because tnftp has already fixed many of the irritations you might associate with old-fashioned FTP.

You get command-line editing, history, and filename completion, things that are also in ncftp. Both understand passive FTP and IPv6. The tnftp client can also retrieve HTTP, HTTPS, and file: URLs, so commands such as:

ftp https://example.com/something.tar.gz

aren’t necessarily typos, although ncftp lacks this ability. But ncftp does have some killer features.

Remember Me?

One of NcFTP’s nicest creature comforts is bookmarks. Connect to a machine, move to a useful directory, and save it:

ncftp /pub/micros> bookmark oldstuff

Then later you can simply type:

ncftp oldstuff

The bookmark can remember more than just the hostname, making frequently used FTP sites feel much more like named resources than anonymous servers you repeatedly have to navigate.

NcFTP also maintains a cache of remote directory listings. If you’ve ever used FTP over a slow link, you know how annoying it is to ask for the same directory listing over and over. NcFTP can often work from what it already knows instead. Neither feature sounds earth-shattering, but together they make an interactive FTP session considerably more pleasant.

Get All The Things

Another difference becomes obvious when you want an entire directory. NcFTP supports recursive transfers:

get -R foo

or:

put -R foo

That seems obvious if you’re accustomed to modern tools, but traditional FTP is fundamentally organized around transferring individual files. NcFTP does the tedious directory walking for you. It also handles resuming interrupted transfers more naturally, something particularly welcome when the file in question is a multi-gigabyte disk image rather than README.TXT. With tnftp, you have to explicitly ask to resume an interrupted file. NcFTP will detect it and, depending on configuration, either resume or, at least, offer to resume the transfer.

Go Away, I’m Busy

NcFTP also has a clever background-transfer system. Commands such as:

bgget giant-file.iso

Hand a transfer to NcFTP’s spooler rather than tying up your interactive session. There are corresponding facilities for uploads. That’s an interesting distinction from simply detaching a shell command. NcFTP knows that this is a transfer job and maintains a queue of FTP work that can be retried and processed independently.

Shell Games

But perhaps the biggest reason to know about NcFTP is that NcFTP isn’t just one program. The package includes commands such as ncftpget, ncftpput, and ncftpls. These perform FTP operations directly from the Unix shell without starting an interactive FTP command interpreter. For example:

ncftpget ftp.example.com /tmp /pub/widget.bin

or:

ncftpput ftp.example.com /incoming widget.bin

This is much nicer in a script than sending commands to ftp using, for example, a here document and automating login with .netrc. For example:

ftp <<EOF
open ftp.example.com
cd incoming
put widget.bin
quit
EOF

Sure, it works, but any time you send input to an interactive program it is, at best, messy. The ncftpput program expresses what you actually wanted to do in the first place: put this file there. That’s much more Unix-like.

Don’t Do This At Home

None of these conveniences change FTP’s fundamental problem: ordinary FTP is not secure. Usernames, passwords, and data can travel without encryption. If you’re designing a new system and control both ends, you usually have much better choices. But sometimes you don’t control both ends. If FTP is something you run into, ncftp is worth knowing about. Bookmarks, cached directories, recursive and background transfers, and especially the script-friendly companion commands turn an antique protocol into something that feels surprisingly at home on a modern Unix command line.

Of course, just as you can use FUSE to mount an ssh server, you can use ftpfs, to make a remote server look like part of your file system. You never know when FTP is going to crop up.

Read the whole story
jepler
22 days ago
reply
I remembe ncftp but I consider lftp to be better (and it supports http and ssh too)
Earth, Sol system, Western spiral arm
Share this story
Delete
Next Page of Stories