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 th
by j-pb 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.
Take https://github.com/ziglang/zig/issues/12251 https://github.com/ziglang/zig/issues/12251 as an example. Several people from the Rust and Haskell community slacked off on twitter about how awful of a language design this is. However, when you think about what the language actually compiles to it makes perfect sense, and is pretty straightforward.
Rust has the goal of putting as much smartness between what you type and what the compiler produces, which is a perfectly fine goal, but without a specification for the input semantics it's a pretty wobbly thing, especially for a systems programming language. A lot of bit packing code is simply not writable in rust today without immediately invoking UB, that works for now but might break with every bugfix release.
- eptcyka 4y agoRust has no explicit goal to introduce magic - instead the goal is to be safe and sound. I'd argue the code very often compiles to what one might expect it to compile to, its certainly better than C++ when it comes to magic in terms of how useful and transparent said magic is.
- j-pb 4y agoC++ is arguably the most complex programming language ever. There is not much to be gained by comparing against the supremum. Take a look at the languages that rust was influenced by (https://en.wikipedia.org/wiki/Rust_(programming_language) https://en.wikipedia.org/wiki/Rust_(programming_language)) those aren't languages with straightforward compilation semantics. There is a reason why rust has a datalog engine build into the compiler (https://github.com/rust-lang/datafrog https://github.com/rust-lang/datafrog). Which is imho totally rad and awesome, but really hard to fully form a mental model of without a spec.
- Rusky 4y agoThis is pretty vague reasoning. Rust draws inspiration from each of those languages for something but that doesn't mean they affect its operational semantics, which are quite similar to C's. The fact that you point to datafrog is illustrative: while it is not actually built into the compiler today, the use case for it is borrow checking, which famously does not impact the language's operational behavior at all! It is purely a compile-time analysis that does not influence code generation. (For example, consider that mrustc and the GCC frontend are both able to omit borrow checking entirely and yet still produce runnable binaries!) Of course, the borrow checker is still something that you need to build a mental model for, but by design it is okay for you to get that wrong occasionally, because the result can only ever be "your program still does what you thought, but the analysis proved that in a way you did not expect" or "your program does not compile."
- pjmlp 4y agoI bet PL/I, Ada, and even Python 3 aren't far off. Same applies to the Java and .NET ecosystem, because either you swim on the surface, or you really get to know how the implementations, down to bytecode, JIT, GC and standard libraries work, and now they are full speed ahead with 6 month release schedules.
- cztomsik 4y agoafter 5ys of (hobby but frequent) rust I'm pretty confident most people cannot tell what their code will compile to and what its runtime profile is going to be (aka you can't reason about performance -> so there is a lot of magic/complexity) I've recently tried Zig and switched to it instantly, it's hard to explain but basically Andrew has a very good taste at picking important features and keeping the language complexity very low. You know how it takes some time to learn borrow checker and macros and generics and traits and all weird rules of what you cannot do and then trait bounds and then it doesn't work exactly like you need, or the crate does not support something and you cannot implement it yourself, etc. etc. So in Zig I had a hello world on day one, and the first thing I did was encode/decode ANY json messages for tagged union (which unfortunately is not supported in std but it was very easy to do it myself) and it worked! I did this the first day in entirely new language (I was not even doing C/C++ before) and it would probably take few days in rust and I'd probably mess up something and I know it wouldn't work for every case and every crate, because of orphan rule. In Zig it would work for any struct, internal or external. And also Debug, Display, Eq, Partial, all of that works automatically. That's huge! And the worst thing is that recently, I've started using pointers again, and when I look at the code I don't see anything unsafe in the structure itself, it can be used unsafely but that is also very easy to fix in Zig because you have these explicit allocator and it's so easy to put everything in the same arena transparently, or use SegmentedList with stable pointers.
- 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.
- burntsushi 4y ago> Rust has the goal of putting as much smartness between what you type and what the compiler produces No it doesn't. "smartness" is a means, not an end or a goal.
- j-pb 4y ago> "It really does feel like rustc is a big step toward the mythical Sufficiently Smart Compiler, letting you write nice code without worrying about a suite of low-level gotchas." > Hear more from Reliability Project Director https://mobile.twitter.com/rust_foundation/status/1385310806498103297 https://mobile.twitter.com/rust_foundation/status/1385310806...
- burntsushi 4y agoLiterally nothing there supports what you said. Don't make shit up.
- j-pb 4y agoDon't take things to literally, the outcome of "we set out to build an arbitrarily smart compiler" and "we build an arbitrarily smart compiler to achieve X" are indistinguishable.
- burntsushi 4y agoNeither has occurred and neither will occur. Again, stop making crap up. The goal of the Rust project is not to inject "as much smartness between what you type and what the compiler produces." That's a gross mischaracterization.
- j-pb 4y agoYou've chosen a really weird hill to get upset about. You also seem to have a way worse connotation for "smart compilers" than I have.
- kmeisthax 4y agoC used to be a "cocky macroassembler", up until people started writing optimizing compilers for it. If your compiler merely translates a source line into a series of assembly mnemonics, function calls, or interpreter gotos, then the interface is the implementation. You can rely on the underlying target language to provide your program with meaning and the only people who have to care are people reimplementing your compiler for compatibility. The moment you start talking about optimization, then this no longer works. You no longer have a correspondence between source and compiled forms of one program. You have a many-to-many relationship where one source form can be compiled into hundreds of binaries depending on how the compiler is configured, and many source forms may actually optimize to the exact same compiled form. This requires you to provide your own semantics, else compiled programs have no meaning and -O3 becomes shorthand for "make demons fly out my nose". In the case of C they came up with a series of rules for what-not-to-do that both did not match existing language semantics and also were dangerously incomplete. There are still C programmers who insist that you can free() memory but still touch it for a "little while"[0], or access memory "off the end" of an allocation[1], for example. And ISO C still made the mistake of retaining pointers, which are a confusing mix of value and reference type. They aren't references because you are allowed to cast them to and from integers; and they can't be values because you can use them to modify other values. Because of this tension, we keep discovering new combinations of valid transformations on valid programs that cause miscompiles, and then we have to invent things like pointer provenance to fix them. As far as I'm concerned, the only difference between Rust and C is that Rust is honest about it's cleverness. C has to pretend to be simple while also out-clevering Rust (or at least, the safe subset of Rust). [0] Usually in an attempt to emulate automatic memory management. Manual memory management does not work when passing complex structures across an API boundary, and the only options are to either expose custom deallocators (which means no optimizations even when they are sound), tell callers how to deallocate the data (which means no changing the data), or hack the allocator to do what you really want. [1] It works for malware developers, it should work for me, right?
- int_19h 4y ago> they can't be values because you can use them to modify other values That seems like an arbitrary criterion. They're values because they behave like values when copying them around etc. You can't even use them to modify other values implicitly - you still need to use * to get an actual dereferenceable/assignable lvalue out of the pointer.
- deleted 4y ago[deleted]
- pcwalton 4y agoYou shouldn't be giving Zig (or C, for that matter) a free pass. If your language isn't memory safe, and it compiles to LLVM with the optimizations on, then it's inheriting most of the complexity of Rust's undefined behavior. (I say "most" because I suspect that Zig doesn't use attributes like noalias as much, but there is still a lot of complexity around, for instance, provenance that are orthogonal to those attributes.) It's LLVM, not the language frontend, that actually implements most of the memory model (via lowering to machine instructions).