3 ms·
- "the instructions it does generate are far more predictable than the ones generated by Rust." I am wondering how that might work? Isn't this extremely depend
by buster 9y ago
- "the instructions it does generate are far more predictable than the ones generated by Rust."
I am wondering how that might work? Isn't this extremely depending on the compiler and flags? To be honest, i would assume that a modern compiler with many optimizations would generate a very different list of instructions then some simple compiler. C has a large list of available compilers..
- "I would love to be able to have the same familiarity, predictability, and indegenous feeling with Rust that I have with C,"
To be honest, all that seems to be mostly time and experience. Surely you won't get the same familiarity with Rust compared to C in a fraction of the time. It just sounds like you feel comfortable enough with C and see no reason to change, which is good for you in a way :)
- ShroudedNight 9y agoThe best I can come up with is examples: enum State { UNINITIALIZED, STARTING, RUNNING, SLEEPING, STOPPING, TERMINATED }; struct Thread { State state; int32_t threadID; MemoryMap * memoryMap; LockTable * lockTable; }; The memory layout of a Thread object is highly predictible: +0 state, +4 threadID, +8 memoryMap, +(12/16) lockTable I can predict the symbol name for a function: Thread * createThread(void *stackArea) I know that stackArea is going to be in RDI, and the resulting Thread* is going to be in EAX (or their well-specified equivalents on another platform) switch (thread->state) { case UNINITIALIZED: case STARTING: error("Not ready yet"); break; case STARTED: sleep(thread); break; case SLEEPING: error("Already asleep"); break; case STOPPING: case TERMINATED: error("Thread is terminating"); break; default: critical_error("The world is on fire"); break; } Given a compiled set of instructions, I can link the compiled instructions corresponding to the switch statement (like a jump table) back to the C code. for(size_t i = 0; i < SOME_MAXIMUM; ++i) { unsigned char const working_value = input1[i]; unsigned char const mask = ((unsigned char) -1) - (working_value >> 7); output[i] = input2[i] &= mask; } I've probably screwed that up, and I don't claim that it's doing anything useful here, but assuming it was doing something useful, I would be able to identify the instruction associated with it, figure out if there was some deficiency present (like say, a conditional) and have a decent idea of how to fix it. Rust, on the other hand, does a bunch of things that are (so far) opaque to me. One example is passing a borrowed slice of something, and somehow, not just a pointer to the thing itself is getting passed, but also seemingly, potentially one or more boundary indices, as well as a lifetime (maybe?) Another example is a match on the type of an object, clearly [on second thought, perhaps not] there's some run-time state being kept around along with the explicitly declared fields inside that object, what does that object look like in a memory dump? As final example: generics, how and when do they get expanded into discrete functions? What do the symbols get named? How do I know if a generic expanion is likely going to overly prolific? Inlining and templates can do wonderous things for performance, but they can also drown it. Anyway, hopefully that wasn't too overwhelming, or ranty. You asked nicely twice; I felt compelled to give you a real answer.