7 ms·
> The major difference between Rust and [Zig, C] is that the latter are essentially "cocky macro assemblers". They derive their semantics from the semantics of
by adwn 4y ago
> The major difference between Rust and [Zig, C] is that the latter are essentially "cocky macro assemblers". They derive their semantics from the semantics of the underlying assembly.
I don't know about Zig, but as for C, this take is not just wrong, but dangerously wrong. The behavior of C code isn't defined by the underlying assembly, but by an abstract machine model that may or may not match your real machine (narrator: It doesn't). As a result, the compiler can and will ignore the intention of your code when it can prove that your code would invoke UB. Good example: signed integer arithmetic and overflow tests. It is very difficult to write overflow tests for signed integer operations without accidentally invoking UB, and modern compilers will simply remove your overflow tests instead of translating them 1:1 to assembly instructions.
- j-pb 4y agoAssembly in the macro-assembler sense. Doesn't matter that it's obscure and not a direct mapping to any specific ISA, there is still relatively straightforward mapping between syntactic constructs and something von neumann-ish. Rust simply lacks that mapping, yet.
- Diggsey 4y agoThis is still wrong though, since UB doesn't exist at an assembly level, and yet C has it. Therefore there is no clear mapping from C to assembly (even considering hypothetical ISAs).
- j-pb 4y ago> UB doesn't exist at an assembly level cough https://riscv.org/wp-content/uploads/2018/05/10.45-clifford-barcelona.pdf https://riscv.org/wp-content/uploads/2018/05/10.45-clifford-... There are plenty of undefined things in ISAs, writing to some internal ARM registries also springs to mind, but that's a bit of a red herring. Just because code with undefined behaviour doesn't map to some abstract machine model nicely, doesn't imply that code that doesn't invoke UB can't have a simple mapping to some abstract machine semantics.
- vintermann 4y agoYes, things like illegal opcodes, or other things the hardware _does_ but isn't officially part of the spec... those things are almost as old as computers. Certainly as old as home computers. The VSP hack on Commodore 64 comes to mind.
- ralfj 4y agoRust has that mapping in the same sense that C does: there exists a fairly simple way to implement every language construct (except maybe for dynamic method invocations) in assembly. However, Rust and C alike have another whole dimension to their semantics, that of Undefined Behavior, which is not reflected in the assembly, and which needs to be taken into account for unsafe code authors (in Rust) / by all programmers (in C). See for example https://www.ralfj.de/blog/2019/07/14/uninit.html https://www.ralfj.de/blog/2019/07/14/uninit.html for what goes wrong when you think of C as just a macro assembler.
- j-pb 4y agoRusts entire trait model is a pretty big deal. The main issue is with memory semantics however, stuff like tagged 64bit pointers are UB land pretty much immediately. MiniRust solves this by providing an explicit memory interface trait.
- ralfj 4y agoYeah okay, traits are super complicated and C doesn't have anything like that, I admit. It's not really part of how to map instructions to the machine, it's more part of how to figure out which function is even being called at a particular call site.
- Rusky 4y ago> The main issue is with memory semantics however, stuff like tagged 64bit pointers are UB land pretty much immediately. MiniRust solves this by providing an explicit memory interface trait. But this is no different from C! The way C programs interact with memory is defined in very similar terms to MiniRust's memory interface trait, using "objects" with some rules for which pointers are allowed to interact with which objects. In both C and Rust, if you want to do pointer tagging or bit packing, you need to consider the language's rules carefully- this does not mean you cannot do it in either case, only that you are stepping close to the boundary of what is supported.
- j-pb 4y ago
- formerly_proven 4y agoIf we understand "straightforward" in the sense that small changes in the input lead to predictable and small changes in the output, then I can't see how this is reasonably true for pretty much any optimizing compiler. Just today we had this nice example on the front page where initializing a variable differently completely changed the codegen for large parts of the program: https://jpieper.com/2022/08/05/debugging-bare-metal-stm32-from-the-seventh-level-of-hell/ https://jpieper.com/2022/08/05/debugging-bare-metal-stm32-fr...
- j-pb 4y agoRust screams UB much quicker when it comes to memory semantics than any other language that I know. Rusts lower bounds of code mangling are also much higher than in other languages. I don't expect to have a good mental model of an optimising compiler in "gotta go fast"-mode, but a mental model of what the memory layout looks like is pretty darn relevant when designing or working with ABIs and binary data representations.
- ralfj 4y ago> Rust screams UB much quicker when it comes to memory semantics than any other language that I know. If you only use raw pointers, Rust has significantly less UB that C does. But if you use references, then yeah we have those aliasing rules and they can be quite restrictive.
- j-pb 4y agoMy brain isn't cached into the issue anymore, because I've since solved the same problem with Zig, but about a year ago there was no way to have a reference counted tagged pointer that points to a boxed DST with the minimum number of bytes. Every path lead through UB or "not yet defined" somewhere. You wrote a PHD on it so you might have a better idea of the issues that past me ran into, than what present me can still recall ;)
- ralfj 4y ago
- adwn 4y ago> Assembly in the macro-assembler sense. Doesn't matter that it's obscure and not a direct mapping to any specific ISA, there is still relatively straightforward mapping between syntactic constructs and something von neumann-ish. That applies to pretty much all imperative languages that compile directly to assembly/machine code – including Rust.
- lmm 4y ago> Doesn't matter that it's obscure and not a direct mapping to any specific ISA, there is still relatively straightforward mapping between syntactic constructs and something von neumann-ish. No there isn't. "Something von neumann-ish" would have behaviour for all inputs - maybe not desirable behaviour, maybe even different behaviour on different processor revisions, but it would have behaviour. The C abstract machine doesn't.