Rewriting m4vgalib in Rust
cliffle.com
cliffle.com
BTW, in your bounds checking examples, I think it can be further simplified (would need to check the output to see if it's any better), this:
fn fill_1024(array: &mut [u8], color: u8) {
// Perform an explicit, checked, slice of the array before
// entering the loop.
let array = &mut array[..1024];
for i in 0..1024 {
array[i] = color;
}
}
Could be: fn fill_1024(array: &mut [u8], color: u8) {
for i in &mut array[..1024] {
*i = color;
}
}
Here's the playground: https://play.rust-lang.org/?version=stable&mode=debug&editio...Thanks again, this is a wonderful piece.
leaq 8(%rsp), %rdi
movl $1024, %edx
movl $255, %esi
callq *memset@GOTPCREL(%rip)
for the 0xFF fill and leaq 8(%rsp), %rdi
movl $1024, %edx
movl $238, %esi
callq *memset@GOTPCREL(%rip)
for the 0xEE fill.However it turns out the original also compiles to that, because the inlining means the fixed size array is visible to the optimiser.
This leads me to believe the shorter approach is actually better.
Yeah but who'd do that?
OTOH I found an "issue" with the playground: the compiler realises that since only a single array is ever created the functions can only ever be called with the proper bounds, so even with inline(never) everything gets compiled down to a memset without bounds check.
And trying to defeat that by modifying buffer to be 1023, the compiler realises the assertions are always going to crash so it creates a weird-ass "bad" version (which it calls), only implements the slicing (and its error) in the base and doesn't even bother outputting code for the simpler version.
Tricksty compiler. Using rand() and slicing so it can't know whether it's getting slices of 1023 or 1024 elements and generating a random fillchar (otherwise the fillchar gets moved into the functions) finally gets it to generate the expected code… and remove one of the two functions, calling the other one instead.
So yeah, at this point I will conclude your version works just as well as the article's at getting optimised.
>And trying to defeat that by modifying buffer to be 1023, the compiler realises the assertions are always going to crash so it creates a weird-ass "bad" version (which it calls), only implements the slicing (and its error) in the base and doesn't even bother outputting code for the simpler version.
With optimizations off, we can see what the compiler is doing before optimization passes potentially obfuscate it. In practice when the call is nested behind many layers it may not be able to elide the bounds check in the loop in all cases; the opt-level may be hiding the truth because it is defeating the test case.
Whether that's true in this case or not is irrelevant, of course; I'd say it's almost always preferable to have better code as early as possible in the code generation process.
> Yeah but who'd do that?
Anyone who does `cargo run`. A project I'm working on right now is slow enough in debug builds that I usually debug it with light optimizations enabled. Lean on the optimizer and you'll find it a leaky abstraction.
The entire discussion starts from looking at getting bounds checks optimised away, not optimising seems counter-productive to this concern?
> lean on the optimiser and you’ll find it a leaky abstraction.
The abstraction here is not really leaky though, observable behaviour would be as expected t’es, it’s when you look at his this behaviour is achieved that things start looking odd.
I'm aware of the history of the conversation, but the point where I joined concerned which idiom performs better unoptimized, a case which is relevant because everybody cargo-runs.
> The abstraction here is not really leaky though, observable behaviour would be as expected t’es, it’s when you look at his this behaviour is achieved that things start looking odd.
In many circumstances performance is part of observable behavior.
> In this series, I'm trying something different. Let's take a grungy, optimized, pointer-type-punning, SSE-using, heap-eschewing, warning-disabling C program and look at how we could create the same program in Rust.
"In C, bodies[i] is exactly the same as *(bodies + i) — it performs pointer arithmetic and a dereference and nothing else. In particular, it assumes that you have some reason to know that i is a valid index for the array. This is the common case in C and gets a shorthand using square brackets."
In C, bodies[i] is not (bodies + i), with the exception being char arrays. The general rule is: bodies[i] = *(bodies + i * sizeof(bodies[0])) #include <stdio.h>
int main() {
int array[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16};
printf("%d %d\n", array[4], *(array + 4));
return 0;
}
prints "4 4".I have to say, this has been amazing. For context, I'm a C++ developer who has had minimal run-ins with the borrow checker because I was already used to thinking about ownership. I refactored a large-ish Rust crate I maintain to use references for a fundamental type. I would have considered it irresponsible to do that with a C++ codebase unless absolutely required. Even if I did the upfront analysis to make sure it was safe, I'd need to re-do that exercise for every architectural change, at least. Instead with Rust, I just tried it on a whim, knowing the borrow checker had my back.
Unfortunately very few care to actually use them. https://www.bfilipek.com/2019/12/cpp-status-2019.html?m=1#wh...
As I learned since Turbo Pascal days, if it isn't part of the language, it seldom gets used.
But also there is a big difference between "the static checker will probably catch errors of this kind" and "the compiler will certainly catch errors of this kind". The latter is far more liberating.
Much like Erlang's concurrency fell out as a consequence of needing to be confidently write high availability code, Rust's general safety falls out of needing to write concurrent code fearlessly.
Rust used to have split stacks and green threads for instance, and had special syntax for GC’d values (and unique pointers TBF).
Also, I quite love the trend of "we rewrote our software in Rust" lately. I loved the speed of C/C++ about 15 years ago but the mounting complexity and the increasingly huge game of whack-a-mole pushed me away from them.
Now I am gradually and casually learning Rust and I absolutely love what I am seeing and experiencing.
The deepest observation in the article is the remark about the cognitive load imposed by need to avoid pitfalls inherent in the programming model.
I coded K&R C in the 80s, and while I never shipped an argument-type bug, avoiding them was a continual distraction. I coded C90 from 2006 to 2012, with a similar cognitive load I was happy to leave behind, although again I never shipped a bug caused by its failings. (We had bugs, but they were specification bugs: building the wrong thing.)
Writing C++ post-14 has been an increasing pleasure. The cognitive load noted is present at times, but decreasingly so as the Standard Library and other libraries get more powerful.
I am disappointed that the author found it too hard to implement a correct program with C++, but more disappointed that he chose to blame his tools for his failure.
It has become fashionable to insist that correct programs cannot be written in this or that unfashionable language, but in fact a great many correct programs have been written in all of them, not excepting octal machine code.
What varies is how much work is needed, and how many people can do it: it is certain that, for any chosen language past or future, most people cannot or will not write a correct program in it. Rust is not different in this.
Many people coding C++, and perhaps most coding C, are not writing correct code, and have over the years written a very great deal of bad code. Will those same people succeed in learning Rust, and learning to write good code in it? It appears that the people currently adopting Rust are more skilled than is typical, so it seems hard to generalize their results to more typical programmers.
C++ and Rust embody different choices in emphasis: Rust, safety; C++, library power. Stewards of C++ have decided the best way to get substantial correct programs is to enable encapsulating semantics in well-tested, well-optimized libraries that can be used without compromising performance or system architecture. Rust has chosen to emphasize making wrong low-level code hard to express, although its ability to express libraries will only increase.
It will not be clear for a long time which will have better results. Will enough people learn Rust to write the correct low-level code needed? Will enough C++ programmers learn to use libraries that make (re-)writing dangerous low-level code unnecessary?
What is clear is that the number of C++ programmers is growing faster than ever, and interest in coding to high level C++ libraries, as measured by attendance at an ever increasing number of conferences, is exploding. Even attendance at each ISO Standard meeting is quite a lot larger than at each previous one, now reliably in hundreds. The number of new C++ programmers in any unit time is still larger than the number of new Rust programmers, by a large factor.
So, the experiment is interesting and valuable. What we can be confident about, either way, is that, for every C coder, or C++ coder writing low-level, often bad code, switching to Rust or to C++ using reliable libraries will result in more good code. It matters much less which they choose.
Also prefix casts have similar issues as prefix type + typedef.
We did take a lot of influence from C, though.
If you look at Rust in the context of other languages, you see that most of the languages designed in the last 10-15 years have made similar syntax choices.
There’s only one true answer to this: “it doesn’t matter at all - I’ll learn it in a week@.
There are numerous examples, but a common one is that humans will usually parse `T x();` as constructing an object of type T with the default constructor, but it actually is a forward-declaration of a function returning T.
Natural languages are enormously harder to parse than any programming language, and people delight in expressing just the constructs that are hardest to parse, yet people do not usually have difficulty communicating with one another. When they do, syntax troubles are vanishingly rarely the reason.
,+[-.[-]-,+]
>>[-]<<[->>+<<]
>++++++++[-<+++++++++>]<.>>+>-[+]++>++>+++[>[->+++<<+++>]<<]>-----.>->+++..+++.>-.<<+[>[+>+]>>]<--------------.>>.+++.------.--------.>+.>+.
So. Now that we've established that syntax "beauty" matters in an extreme case, can we agree that people can have different opinions about where the line is?Can we then agree that having an opinion different than your own does not make someone an inherently less senior(???) or capable developer?
I have a strong opinion that you should like everything. Are you going to make a point that your genetic makeup somehow prefers tabs over spaces? :)