My point is against rewrites of critical software for the point of rewriting it *insert my favorite language*. Zig is also a safer language than C, so are many other alternatives, yet the Zig community is not obsessed in rewriting old software but writing new one. And the Zig compiler has excellent C interop (in fact it can compile C/C++), yet the community is more focused in writing new software.
There are many factors that make software reliable, it's not just a matter of pretty types and memory safety, there's factors like platform/language stability, skill and expertise of the authors, development speed and feedback.
In this specific case we are talking about the maintainer adding a new language into the existing codebase.
I think refactoring parts of the software in the new language is what you call "rewrite" here, correct?
So what improvements does it bring? You actually answered it yourself:
> it's not just a matter of pretty types and memory safety
So indeed, stricter/stronger type system and additional automatic compile time and runtime checks are a major improvement.
> platform
As already mentioned in this thread: neither of the platforms lacking Rust were supported officially anyways.
> language stability
Rust is extremely stable and backwards compatible - 1.0 code still compiles without any issues on 1.90 and will continue to do so for the forseeable future.
> skill and expertise of the authors
The same developers continuing to contribute and newcoming developers have more checks in place to prevent bugs.
> development speed
I guess you imply here that developing in C++ is faster. It's in fact not if your aim is to produce correct software. There are so many more things to keep in mind and take care of with C++, you have fewer automatic checks being done by the compiler and the type system.
About Zig: it's a nice language and much more comfortable to use than C/C++ IMO, but compared to Rust it lacks in strictness and safety, so added benefits are smaller and fewer if you put away subjective preferences.
There's lunatics that want to replace basic Unix tools like sudo, etc, that are battle tested since ages which has been a mess of bugs till now.
Instead Rust should find it's niches beyond rewriting what works, but tackling what doesn't.
> There's lunatics ...
I think the problem is people calling developers "lunatics" and telling them which languages they must use and which software they must not rewrite.
Battle tested is not bulletproof: https://cybersecuritynews.com/sudo-linux-vulnerability/
Applying strict compile time rules makes software better. And with time it will also become battle tested.
You can't go around screaming "your code SUCKS and you need to rewrite it my way NOW" at everyone all the time and expect people to not react negatively.
It seems you are imagining things and hate people for the things you imagined.
In reality there are situations where during technical discussions some people stand up and with trembling voice start derailing these technical discussions with "arguments" like "you are trying to convince everyone to switch over to the religion". https://youtu.be/WiPp9YEBV0Q?t=1529
I am more concerned about correctness and "zero-cost abstraction" of the end result, if sacrificing few seconds of compilation time is necessary for that - fine by me.
> It’s complex. Just as complex as C++. But C++ had legacy and Rust had not. The complexity of forcing your way through the jungle of Arc<Mutex<Box<T>>> on every single step directly impacts the quality of the logic being implemented i.e. you can’t see the forest for the trees. Once again, C++ has the same problem, so what’s the point of the language switch in the end?
So, you seem to prefer data races and dangling pointers, or? Have you tried "actors-like" channels+rayon approach if you hate mutex so much?
> Memory safety is not that sacred. In fact, for many applications malfunctioning is better than crashing — particulary in the embedded world where Rust wants to be present. You cannot get 99.999% reliability with Rust — it crashes all the time.
You again prefer UB and/or corrupted state (and call that "reliability") over required explicit checks of Result via matching. This is definition of bad code to me.
> When handling lots of mutable shared state (GUI, DB, stateful services, OS/hardware), the performance of native Rust memory model is subpar
Few boundary checks sometimes add negligible overhead, but again you need those in any language to enforce correctness, Rust just adds those automatically.
Do note that C did not have such a flaw built into language — C++ authors invented it and Rust inherited this flaw (the authors simply did not feel like it's a flaw). I mean specially designed embedded C code can survive total RAM erasure and still perform some meaningful work (with CPU registers and ROM intact). Or compare it to BEAM that can have processes crash all day long and still continue to work. "Memory safety at all cost" is not a practical requirement — it's theological.
Recoverable error mechanism in Rust: https://doc.rust-lang.org/std/result/enum.Result.html
> Memory safety is not that sacred. In fact, for many applications malfunctioning is better than crashing — particulary in the embedded world where Rust wants to be present. You cannot get 99.999% reliability with Rust — it crashes all the time.
Would you prefer silent UB/memory corruption in Cloudflare case? I don't think most developers would agree with this.
There is a full-fledged DE written in Rust that uses Iced: https://en.wikipedia.org/wiki/COSMIC_(desktop_environment)
use std::env;
fn main() {
let args: Vec<String> = env::args().collect();
...
}
When I run this code, a literal example from the official manual, with this filename I have here, it panics: $ ./main $'\200'
thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: "\x80"', library/std/src/env.rs:805:51
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
($'\200' is bash's notation for a single byte with the value 128. We'll see it below in the strace output.)So, literally any program anyone writes in Rust will crash if you attempt to pass it that filename, if it uses the manual's recommended way to accept command-line arguments. It might work fine for a long time, in all kinds of tests, and then blow up in production when a wild file appears with a filename that fails to be valid Unicode.
This C program I just wrote handles it fine:
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
char buf[4096];
void
err(char *s)
{
perror(s);
exit(-1);
}
int
main(int argc, char **argv)
{
int input, output;
if ((input = open(argv[1], O_RDONLY)) < 0) err(argv[1]);
if ((output = open(argv[2], O_WRONLY | O_CREAT, 0666)) < 0) err(argv[2]);
for (;;) {
ssize_t size = read(input, buf, sizeof buf);
if (size < 0) err("read");
if (size == 0) return 0;
ssize_t size2 = write(output, buf, (size_t)size);
if (size2 != size) err("write");
}
}
(I probably should have used O_TRUNC.)Here you can see that it does successfully copy that file:
$ cat baz
cat: baz: No such file or directory
$ strace -s4096 ./cp $'\200' baz
execve("./cp", ["./cp", "\200", "baz"], 0x7ffd7ab60058 /* 50 vars */) = 0
brk(NULL) = 0xd3ec000
brk(0xd3ecd00) = 0xd3ecd00
arch_prctl(ARCH_SET_FS, 0xd3ec380) = 0
set_tid_address(0xd3ec650) = 4153012
set_robust_list(0xd3ec660, 24) = 0
rseq(0xd3ecca0, 0x20, 0, 0x53053053) = 0
prlimit64(0, RLIMIT_STACK, NULL, {rlim_cur=9788*1024, rlim_max=RLIM64_INFINITY}) = 0
readlink("/proc/self/exe", ".../cp", 4096) = 22
getrandom("\xcf\x1f\xb7\xd3\xdb\x4c\xc7\x2c", 8, GRND_NONBLOCK) = 8
brk(NULL) = 0xd3ecd00
brk(0xd40dd00) = 0xd40dd00
brk(0xd40e000) = 0xd40e000
mprotect(0x4a2000, 16384, PROT_READ) = 0
openat(AT_FDCWD, "\200", O_RDONLY) = 3
openat(AT_FDCWD, "baz", O_WRONLY|O_CREAT, 0666) = 4
read(3, "foo\n", 4096) = 4
write(4, "foo\n", 4) = 4
read(3, "", 4096) = 0
exit_group(0) = ?
+++ exited with 0 +++
$ cat baz
foo
The Rust manual page linked above explains why they think introducing this bug by default into all your programs is a good idea, and how to avoid it:> Note that std::env::args will panic if any argument contains invalid Unicode. If your program needs to accept arguments containing invalid Unicode, use std::env::args_os instead. That function returns an iterator that produces OsString values instead of String values. We’ve chosen to use std::env::args here for simplicity because OsString values differ per platform and are more complex to work with than String values.
I don't know what's "complex" about OsString, but for the time being I'll take the manual's word for it.
So, Rust's approach evidently makes it extremely hard not to introduce problems like that, even in the simplest programs.
Go's approach doesn't have that problem; this program works just as well as the C program, without the Rust footgun:
package main
import (
"io"
"log"
"os"
)
func main() {
src, err := os.Open(os.Args[1])
if err != nil {
log.Fatalf("open source: %v", err)
}
dst, err := os.OpenFile(os.Args[2], os.O_CREATE|os.O_WRONLY, 0666)
if err != nil {
log.Fatalf("create dest: %v", err)
}
if _, err := io.Copy(dst, src); err != nil {
log.Fatalf("copy: %v", err)
}
}
(O_CREATE makes me laugh. I guess Ken did get to spell "creat" with an "e" after all!)This program generates a much less clean strace, so I am not going to include it.
You might wonder how such a filename could arise other than as a deliberate attack. The most common scenario is when the filenames are encoded in a non-Unicode encoding like Shift-JIS or Latin-1, followed by disk corruption, but the deliberate attack scenario is nothing to sneeze at either. You don't want attackers to be able to create filenames your tools can't see, or turn to stone if they examine, like Medusa.
Note that the log message on error also includes the ill-formed Unicode filename:
$ ./cp $'\201' baz
2025/08/22 21:53:49 open source: open ζ: no such file or directory
But it didn't say ζ. It actually emitted a byte with value 129, making the error message ill-formed UTF-8. This is obviously potentially dangerous, depending on where that logfile goes because it can include arbitrary terminal escape sequences. But note that Rust's UTF-8 validation won't protect you from that, or from things like this: $ ./cp $'\n2025/08/22 21:59:59 oh no' baz
2025/08/22 21:59:09 open source: open
2025/08/22 21:59:59 oh no: no such file or directory
I'm not bagging on Rust. There are a lot of good things about Rust. But its string handling is not one of them.
- better data race handling;
- no garbage collection, more predictable performance;
- stricter type system that can enforce constraints better;
- closer to metal and better FFI support.
Which seems to matter more for Tor project than support for some barely used platform.