I think it's a monoculture phenomenon. Rust has a website and a pitch; C++ does not. Rust has a single compiler and one way to do things; C++ is overly diverse. Rust has learned from npm, etc. and has made package management first-class with a single package manager; C++ has not. Rust is just a much more familiar experience for developers coming from other monoculture languages.
Unless you have to interface with a lot of mostly-header/header-only libraries written in C or C++, Rust is just so much more productive to work with than C or C++, even if you don't have to worry about memory unsafety.
I find C++ more productive. In C++ I mainly fight with template error messages. In Rust, I fight with typechecked generics, inserting & and '_ here or there, adding and deleting imports, refactoring to add Some and Ok - tons of nonsense housekeeping. These features have value but are quite a slog when writing code.
Some of the kinds of bugs that are much easier to accidentally write in C++ than in Rust are: use-after-free, use-after-move, out-of-bounds array accesses, data races and other synchronization errors. I want my code to be correct, even if it runs in a sandbox.
But the great strengths of Rust, like memory and thread safety, are blunted in WASM, which is already memory-safe and thread-crippled. So Rust's success in WASM must be due to other factors.
I'm confused by that statement. Do you not care whether your code works correctly? Do you consider finding and fixing bugs to be separate from writing code?
> "upstream crates may add a new impl" I have satisfied the compiler but no bugs were prevented.
Trial-and-error debugging which version causes a bug is busywork, preventing it right when someone introduces the potential problem is at best annoying, but very fast compared to the alternative.
https://play.rust-lang.org/?gist=84883cb7cdd09acdcd919888cef...
It's especially bad if your type is an enum, since there's no obvious place to put the PhantomData:
https://github.com/rust-lang/rust/issues/32739
> Trial-and-error debugging which version causes a bug is busywork, preventing it right when someone introduces the potential problem is at best annoying
It's a silly limitation. For example, u64 and u128 are not From<usize> because...well I have no idea. But you can't make them From, because Rust wants to reserve the right to make them From in the future. And you can't write a function that assumes they are NOT From, for the same reason.
So it's pointlessly hard to write generics over integers. I encounter lots of weird holes like this.
https://play.rust-lang.org/?gist=600a1ca784ee7df02351e58df43...
> For example, u64 and u128 are not From<usize> because...well I have no idea.
Because usize is not known statically, it's target arch dependent. Yes, it's silly, but that's how technical safety works. (Also, just as a silly technical counter example the AS/400 virtual instruction set has 128 bit sized pointers.)
There's no question about the need for more ergonomics. That's what this whole post is about after all. For example in some cases where currently PhantomData is needed the intent of the programmer can be easily and unambiguously figured out, but for this folks need to be at least sufficiently certain that this makes things easier, makes code readable, doesn't constraint later language evolution, etc. (If I understand correctly some associated trait bound enhancements will lead to more readable code, less PhantomData.)
Sandboxing only secures the boundary between the WASM interpreter and the embedding application (typically the browser). You can still perform significant exploits within the sandbox. See [0]
IIRC, low-level languages need to maintain a shadow stack in the heap because WASM has no native support for stack variable pointers and without ASLR, we're inching dangerously close to classic buffer overflow attacks. Rust still buys you safety in that regard.
[0] https://www.usenix.org/conference/usenixsecurity20/presentat...
Instead I believe they are choosing Rust/WASM because of the Rust ecosystem: familiar package management, tutorials, other resources.
... ?
The web is already full of untrusted JS. WASM is for performance. The added extra security is just the cherry on top.
Indeed, but porting to C# does. For many practical applications, modern C# is already a language with C performance. The only large area where C# lags behind is SIMD. They made a good progress in .NET 3 and 5, but still somewhat worse than C/C++ with intrinsics.
Here’s a library which implements media player component for ARM Linux: https://github.com/Const-me/Vrmac/tree/master/VrmacVideo#per... It calls native code to present decoded video frames with GLES, and decode audio with third-party DLLs. Also consumes kernel C APIs like V4L2, ALSA, message queues from librt.so, and a few things from libc.so. Everything else is in C#: file I/O, containers parsing, decoders configuration, multithreading, buffering, A/V sync. The performance is same as VLC player which is written in C.
C++ and Rust have runtimes as well. For C++ that’s normally a DLL like libstdc++ or msvcrt. CLR is larger but still reasonable, for 64-bit Windows VC_redist.x64.exe is 14 MB, dotnet-runtime-3.1.13-win-x64.exe is 25 MB.
> and is also garbage collected by default
By default yes, but that’s avoidable. Modern .NET with their spans, value tuples, ref structs, ArrayPool, etc., make it relatively easy to write C# code which doesn’t allocate much. Or even at all, as you can see on the link in my previous comment.
Even supposing you still mange to write a whole program that doesn't allocate dynamic memory, or that quickly stabilizes around a finite `ArrayPool`. You're still pulling in all the runtime GC machinery. Further, the minute someone _else_ contributes code, you run the risk of allocating again.
As much as I love C#, _this isn't its niche_.
A lot of APIs in modern .NET already supports spans. They can be backed by anything, not just GC-managed memory.
For that project, some of these spans are backed by the memory mapped by V4L2 or ALSA API calls. Others are backed by unmanaged buffers allocated on startup with Marshal.AllocHGlobal. For small temporary stuff I use stackalloc, e.g. both mp4 and mkv use tons of variable-length encoded integers.
> the minute someone _else_ contributes code, you run the risk of allocating again.
Good point. On the other hand, someone else contributing code can introduce any bugs at all, not just GC-related performance issues.
> As much as I love C#, _this isn't its niche_.
A while ago some people were saying the same about C, and were instead coding assembly :-)
task.ContinueWith( t => { }, TaskContinuationOptions.OnlyOnFaulted );
However, graceful cancellation is indeed hard. Doable in .NET but not easy, you gonna need to manually pass these cancellation tokens all the way down.Do you have an example of a language/runtime/etc where the cancellation's easy? It's easy to kill processes from the outside, but even for threads it's borderline impossible without horrible side effects.
Rust's embedded and OS-level capabilities are a big point in its favor, particularly over something like C#.
Even in embedded, for some products the tradeoff is good enough. A while ago I’ve shipped embedded software that uses .NET Core and runs on RK3288, worked quite well for us.
BTW, do you have good experience with Rust on bare metal STM32? I would expect STM-supported C toolset and libraries to be generally better?
That said, CubeMX's configuration tool is very nice. But that's used (hopefully) once at board bringup and never again, so not worth sticking with C for.
https://devblogs.microsoft.com/aspnet/grpc-performance-impro...
So when we dive into C++ to write a small DLL/.so to be consumed by those languages, it is basically for doing exactly the same that would be a bunch of unsafe code blocks in Rust.