And here I thought rust was all about zero cost abstractions.
And here I thought rust was all about zero cost abstractions.
If you want to combine the two concepts, I guess you could go for an example like this:
if index >= vec.len() {
panic!("out of bounds");
}
let value = unsafe { vec.get_unchecked(index) };
which can be rewritten using higher level abstractions as: let value = vec[index];
The second is more abstract, and that abstraction is zero cost, you do not pay for using the abstraction more than the explicit code above it. pub fn decode_12be(buf: &[u8], width: usize, height: usize) -> Vec<u16> {
decode_threaded(width, height, &(|out: &mut [u16], row| {
let inb = &buf[(row*width*12/8)..];
for (o, i) in out.chunks_exact_mut(2).zip(inb.chunks_exact(3)) {
let g1: u16 = i[0] as u16;
let g2: u16 = i[1] as u16;
let g3: u16 = i[2] as u16;
o[0] = (g1 << 4) | (g2 >> 4);
o[1] = ((g2 & 0x0f) << 8) | g3;
}
}))
}
The "decode_threaded()" call is a function call that passes in a closure with the inner loop to a generic function that is used to multithread a bunch of similar decoders, instead of repeating that code. And the for loop is actually describing what I want (process every 3 bytes into 2 output values) instead of having me manage some iteration variables and have off-by-one bugs. "chunks_exact" and "chunks_exact_mut" are recent additions that allow me to say that I only want to receive exactly 3 bytes and output exactly 2 values, so if the array is improperly sized the extra at the end just gets skipped. This not only matches the intention of this code (I only ever process 2 pixels from 3 bytes and nothing else will work) but also gives the compiler a better way to lift the bounds checks out of the inner loop and make the code significantly faster (by 2x in some decoders).Now let's see "decode_threaded":
pub fn decode_threaded<F>(width: usize, height: usize, closure: &F) -> Vec<u16>
where F : Fn(&mut [u16], usize)+Sync {
let mut out: Vec<u16> = alloc_image!(width, height);
out.par_chunks_mut(width).enumerate().for_each(|(row, line)| {
closure(line, row);
});
out
}
Besides allocating the image it uses "par_chunks_mut" to make the code threaded. That's a function that's provided by the rayon crate which manages a threadpool and it's scheduling for me. So I'm even using code written by other people, to build something that's apparently deeply nested even though it needs to be fast. And yet after all this indirection and syntax goodies the end result is efficient machine code that matches or is even better than the original C++ code and not the dynamic runtime like behavior of Ruby/Python that you'd expect from such code.That's what zero cost abstractions are. After memory and data race safety I think rust shines because it gives me a lot of the ergonomics of Ruby with the speed of C.
if you can't use iterators, and you need to omit bounds checks for perf, you can do so with unsafe blocks.
Rust requires a lot of runtime checks, but that's the price one has to pay for memory safety.
Even better is to use iterators and other abstractions that don't require bounds checking at all. Rust has lots of good tools to build efficient code.
But the real benefit of Rust is ownership tracking - and that one is zero overhead.
"C++ implementations obey the zero-overhead principle: What you don't use, you don't pay for".
In Rust you pay for e.g. bounds checking or integer overflow handling or optionals. But I don't understand why everyone's getting so defensive about this, since it's the only way (static verification and proofs aside) to get the desired safety characteristics...
You can disable them in debug builds as well with wrapping integers. So this is again a practice of making the safer option the default, but won't incur runtime costs if it bothers you.
If there is a way to do it at compile time that will usually be used in rust.
It requires a lot of runtime checks on the boundary between rust and C/C++ code. You cannot trust C/C++ code, so you are forced to check everything. C-function returns a enum value? Check that this value is in the enum, before converting it to the rust enum. Check that there are no situations like
enum MyEnum {
val1, val2=5
};
enum MyEnum foo() {
return 42;
}
But after you've done all that checks you need to check almost nothing, because if you have a enum value in Rust, you know for sure that it is a valid value. And compiler knows that the value is valid and optimizes accordingly. If you have an &str, you know that it is a valid utf8, you need not to check it on each access, it have been checked already. You got a valid pointer from C? Wrap it into NonNull, so neither you, nor compiler would need to check validity of a pointer once more.It is unclear thing, who needs more checks -- safe code or unsafe code. I'm gravitating to a belief that unsafe code needs much more runtime checks.