pub fn insert(&mut self, index: usize, elem: T) {
// Note: `<=` because it's valid to insert after everything
// which would be equivalent to push.
assert!(index <= self.len, "index out of bounds");//THIS ASSERT
if self.cap == self.len { self.grow(); }
unsafe {
// ptr::copy(src, dest, len): "copy from src to dest len elems"
ptr::copy(self.ptr.as_ptr().add(index),
self.ptr.as_ptr().add(index + 1),
self.len - index);
ptr::write(self.ptr.as_ptr().add(index), elem);
self.len += 1;
}
}
If I understand correctly, in "non-debug" mode we have UB (if the index is > self.len). The assert is elided from that build. Am I correct?
Thanks, that comes as a surprise to me (that assert is present in non-debug). We learn something new everyday - those one who are lucky that is!
Best regards!
You can't trigger UB in safe Rust even by mistake or serious bugs on your code. With bugs the program may not work as expected, but at least it won't trigger UB and work as written. With UB the program may not work as written.
The code uses unsafe but it's a safe function so it can be called in safe Rust without unsafe keyword on your code. Bug in unsafe code may trigger UB so you need to write it with much more care, add extensive tests and make it checked by enough eyeballs. It is generally believed that the stdlib meet these criteria and reliable(but it still has some bugs like every other nontrivial softwares and we regularly fix it).
Safe functions using unsafe code MUST NEVER triger UB in every imaginable possible cases. If someone managed to trigger UB by writing purely safe Rust, it's due to the bug from unsafe code and should be fixed there.
The point is, when some unsafe code uses assert!() to enforce a soundness invariant, that is sound, because assert!() is never removed. It would not be sound (and thus the standard library wouldn't do this) if assert!() were removed in release builds, as this would allow callers of safe functions to bypass assertions (and thus cause UB), which is the very definition of unsoundness.
Asserting a function's preconditions, like here, is incredibly common and important in Rust. It allows the rest of the function to know that something is true, allowing both unsafe code and the optimizer to do better based on that information.
My favourite examples is that the assert makes the following code way better:
Not only does it mean that the caller gets a more helpful message if they got it wrong, but having the assert there means that LLVM removes the bounds checks from each of the three array accesses. The code with the assert is both faster and smaller!
Thanks, that is really, really interesting and helpful. I was not aware of the fact that llvm can deduce that purely basing it on that assert. If that is correct what you're saying then it is really great news!
Generally, LLVM is very smart, and you can trust that it will notice trivial things such as "if this array has length 3 or more, I don't need to check that indices 0, 1, and 2 are valid separately". A much more interesting question to ask is what it can't do, because then you'll potentially need to write clever (or at least different) code to make it optimize well.
Modern compilers rely a whole lot on optimizations to remove cruft introduced by collapsing abstractions. Consequently, your default assumption should be that code is optimized aggressively, and trivial inefficiencies like the one above are almost always completely removed.
See this for more information (the talk is "about C++" in theory, but not much other than surface syntax is specific to C++. LLVM optimizes IR generated from Rust the same way it optimizes IR generated from C++.)
Compiling playground v0.0.1 (/playground)
warning: dereferencing a null pointer
--> src/main.rs:4:28
|
4 | println!("{}", *std::ptr::null::<i32>());
| ^^^^^^^^^^^^^^^^^^^^^^^^ this code causes undefined behavior when executed
|
= note: `#[warn(deref_nullptr)]` on by default
warning: `playground` (bin "playground") generated 1 warning
Finished dev [unoptimized + debuginfo] target(s) in 4.05s
Running `target/debug/playground`
timeout: the monitored command dumped core
/playground/tools/entrypoint.sh: line 11: 8 Segmentation fault timeout --signal=KILL ${timeout} "$@"
Of course you might say the UB is caused by buggy and not by foo. But still, foo kinda "triggers" the UB (which might have not been happening otherwise). In a way, all functions in the call stack could be understood as "triggering" the UB (even though buggy is the only function to "blame" because it is unsound).
Rust's safety guarantees only hold if every function that uses unsafe code is sound.
I'm saying this because when you have a huge dependency tree, it might be possible that one of your dependencies has unsound code and may let you trigger UB even with safe Rust.
Unfortunately godbolt doesn't confirm that. Assembly generated with and assert is larger than without it and that would suggest that the optimizations because of that assert were not performed.
Link:
You need to enable optimizations. (and forbid inlining and actually do something so that main won't be just a ret, or make the function pub without a main)
You can see it with this godbolt link. With the assert uncommented, as in the link, you have the assertion check, followed by the obvious chain of instructions to do the additions (with carry for the high half).
Commenting the assert results in much more code - it starts with three bounds checks (for 0, 1 and 2), then does the obvious chain of instructions for the additions.