My reference was dropped, why is the compiler complaining about multiple borrow
ntietz.com
ntietz.com
The code example is Rust written as if it were classic C. In Rust, that example would usually be written like this:
/// Find leading zeros in an array of bytes.
fn find_leading_0s(bytes: &mut [u8]) -> Option<&mut [u8]> {
if let Some(pos) = bytes.iter().position(|v| *v != 0 ) { // search
Some(&mut bytes[..pos]) // find
} else {
None // no find
}
}
Rust isn't a fully functional language, but the functional features play well with the lifetime system and are optimized well.(Rust playground example: https://play.rust-lang.org/?version=stable&mode=debug&editio...)
Probably need to release it as a patch so as not to need approval of the purity gatekeepers. People using it get an immediate boost in productivity as it enables exploration with no ultimate reduction in safety.
It doesn't matter whether it "works for certain inputs", or for any inputs. The point is to be able to direct attention to more immediately important algorithmic matters now, and then address borrow checker pedentism once actually difficult questions are resolved. The point is not to be able to ship code with borrow violations. It is hard for me to understand what is so difficult about this distinction.
The idea to use it during rapid prototyping is especially problematic, since that's the point where pernicious data access patterns will infect the entire design. Once the application "works, save from some double free bugs in weird testcases we don't expect in production, and which require a one month refactoring", the temptation to ship the flag into production will become overwhelming.
But on the other hand
- Rust already has unsafe mode, which is exactly for allowing you to avoid some safety checks when you've decided you are smarter than the compiler
- this really is an example where the compiler isn't smart enough, as evidenced by the fact there are plans to make it work
So another "unsafe" type of marker that watered down the borrow checker would not be totally inconsistent with the language philosophy. It should work at the block or the file level, though - surely you wouldn't want to turn that off for all your code.
It could work by replacing any function with type errors (including those caused by type definitions) to panic on use, but otherwise still generate a binary. For type errors it is less useful than for lifetime errors because usually the problems of the former are very local while for the later it they are not.
Original: https://godbolt.org/z/rPfEWPGc1
It’s cool to see the same code be generated. You can use “vec!” To stop it specializing the function in the length
Ideomatic form, inner loop: [1]
LBB4_3:
cmp byte ptr [rax + rcx], 0
jne .LBB4_4
inc rcx
cmp rdx, rcx
jne .LBB4_3
C-like form, inner loop: [2] .LBB4_3:
cmp byte ptr [rax + rcx], 0
jne .LBB4_4
inc rcx
cmp rdx, rcx
jne .LBB4_3
Both ways, the compiler found the optimal solution.
In the C-like loop, it was able to hoist and then elide the subscript check.
In the ideomatic form, it's implicit in the iterator that you won't go off the end of the array.
The lambda function was expanded inline and optimized.
There's zero call overhead from using a lambda function there.Very nice work by the Rust compiler people and LLVM.
My project has not moved much this last week due to an issue I just can't understand. Have rewritten the offending code 4 times but it always fail to compile somehow.
At this point, I am only staying with Rust because of the ergonomics of enum and iterators. The memory safety stuff is 90% writing safe code then fighting the compiler to agree with you.
:(
This isn’t my experience, from a decade of occasionally watching and helping others learn Rust, personally and professionally. Rather, it’s normally writing code that you believe is safe, wrestling with the compiler and finally realising that it was right all along, or giving up before reaching this stage but it was still. There are certainly some cases where this isn’t the case, and this article talks about the most common and most notorious one, but seriously, the compiler is normally right.
Somewhere along the way, things click and your intuitions subsequently almost always match the compiler’s, and you have a much happier time of it, seldom needing to wrestle. And your coding in other languages tends to improve, or at least focus on ownership more in ways that tend to make later maintenance easier.
Me, writing it in C: Grumble, grumble, la-la-la.
Me, writing it in Rust: I HATE THIS THING WHY DO YOU oh hey I'd been thinking about this all wrong.
I go through that cycle all the freaking time. At first, I'm irked that Rust is being such a pain in the neck. Then I give in and do things the way it wants me to, and realize that it's genuinely much better that way, even if I then take that approach back to Python. At the least, now I'm more aware of when I'm counting on the garbage collector to atone for my sins.
Don't other languages have things that are close enough?
Maybe give ocaml or haskell a try :)
A lot of the borrow checker problems go away if you're ok with allocations, storing trait objects on the heap, making your ADTs not have references, etc. But getting familiar with the language enough to know "this requires unsafe or for<'a>" takes a while.
Some code can be safe but is hard to reason about. The compiler tells you that. The solution is, unsurprisingly but perhaps disappointingly, to stop writing code like that.
This is a point I made back when I was doing verification. Yes, you can write code for which termination cannot be proven. If termination for your code is theoretically undecidable, it probably won't work right anyway. So don't go there.
Microsoft took a hard line on this with their Static Driver Verifier, a proof of memory correctness system for Windows kernel drivers. If your driver code is so hard to analyze that automatic path analysis can't verify memory correctness, it doesn't get signed to run in kernel space. Driver-caused Windows kernel crashes, formerly a huge problem, mostly went away.
But that is not easy, and much of that is due to the complexity of Rust and the way it handles memory safety. Which is where fighting the compiler comes in.
It is super hard if you actually know C/C++ or mastered a GC language. Rust won’t like some patterns at all and will fight you.
The point is, there are replacements for those patterns that are safer, about as performant and simultaneously easier to reason about by both you and the compiler. To stop fighting, you must switch to them. Old ways will not work. This is by design and a key point of Rust.
A single, concrete example: do not use references in object graphs. Use an array and indexes into it. If you have a dynamic structure with many additions and deletions, use generational indexing.
I find that a lot of Rust borrow checker problems can be solved by inserting more blocks. At the very least, it helps you isolate precisely where the issue is.
I don't see this recommendation often for how much it seems to help beginners. To be fair, it's a lot less of an issue than it used to be as the borrow checker has gotten quite a bit better.
The author goes into a bunch of other issues or uses his own terminology, which makes things even more confusing.
(Not sure what you are referring to wrt the terminology)
For some reason, I’ve always had immense trouble with index bound arithmetic (questions like: is this inclusive? Exclusive? Is ..0 a single element or none? What about ..=0?). I have to actively think about these as I do when telling East from West. North versus South is simply intuitive, no conscious thought needed. Weird effect.
Glad to see I wasn’t wrong in stumbling over this particular one.
Also, be careful about how you zero it, so the compiler doesn't optimise it out.