Rust itself has a bunch of guarantees. Rust's vector is guaranteed to work well within that framework. So,
- No dangling pointers / UAF from stuff it gives you
- No iterator invalidation from iterators you get from it
- You can't push to it and invalidate references to its insides (this is really the iterator invalidation one over again)
- You can't use it in a way that will not be data race safe
These are all Rust guarantees; Vec just adheres to them. You can say that these guarantees are Vec guarantees too (which is why I'm saying "I'm not sure what you're asking for).
c++ std::vector does not have these guarantees because c++ does not have these guarantees. You can get some of these guarantees by restricting yourself to coding a certain way but they kinda fall apart when stuff gets complex in my experience.
Aside from that it's basically the same guarantees as std::vector, i.e. "will store stuff in contiguous space", "will allocate buffer with extra capacity as it grows" (this isn't actually a guarantee, but it's not going to change for either).
One thing Rust's vector guarantees that C++ doesn't is that Rust's vector will not store elements inline; anything (with nonzero size) you push into it will be heap allocated.
Rust's vector also never implicitly performs deep copies, but this isn't really a guarantee.
Rust also has a concept of zero sized types, which C++ does not, and Rust's vector deals with them in a certain way, however since C++ doesn't have these types you can't really compare.
It's possible C++ guarantees a drop order for where Rust's vector does not. I'm not sure.
I would assume that in Rust it would fall apart just the same, except perhaps at compile-time rather than at run-time?
No, because Rust gives you the tools to compartmentalize this stuff pretty well. Yes, if you write an unsound program it will fail to compile, but the language also guides you in the direction of not writing one, so huge complicated codebases written in 100% safe rust are possible and do compile. With C++ as complexity grows it's harder to keep stuff safe, in my experience. YMMV.
Very little, so please bear with me ;)
For example, from the linked page:
> Most fundamentally, Vec is and always will be a (pointer, capacity, length) triplet. No more, no less.
Does C++ also provide this guarantee?
> Vec will never perform a "small optimization" where elements are actually stored on the stack for two reasons:
Does C++ do this?
and from your response:
> No iterator invalidation from iterators you get from it
How are you guaranteeing this? C++ doesn't, so what is going on behind the scenes to make this work in Rust?
As you can see, I don't know enough about C++ as I would like either…
I don't think it does. It might.
> Does C++ do this?
IIRC C++ is allowed to perform the optimization. I don't recall if any current implementations do. They might.
Edit: I'm wrong, it's not, however std::string is allowed to.
> How are you guaranteeing this? C++ doesn't, so what is going on behind the scenes to make this work in Rust?
Because Rust has an entire system of ownership and borrowing which exists at compile time to ensure these things don't happen.
If you don't have a mutable borrow active, you can immutably borrow it as many times as you like and iterate over it to your heart's content in as many scopes as you like, but you can't mutate it.
These borrows are lexically scoped.
This is a start to explaining ownership: https://doc.rust-lang.org/book/second-edition/ch04-01-what-i...
Basically, in Rust, this happens:
let x = vec![1,2,3,4];
let iterator = vec.iter();
vec.push(5); // will not compile, until iterator goes out of scopeThat means you can think of it as push(&mut vec, 5) - you need to pass a mutable reference to the vec as the first a argument and the iterator is already holding one of them. So you're not allowed to.
The iterator is an immutable reference. push() is a mutating method. These don't work together.
Rust is "aliasing XOR mutability"
They are close to being equivalent, but maybe with weird-sized objects, the (pointer, pointer, pointer) version requires some divisions, and the other one only requires multiplications?
The reason is that std::swap(v1, v2) must never invalidate existing iterators and/or references to elements into either vector, which the optimization would clearly break.
std::string is the type you may be thinking of, where the standard does make a special exception to enable the SSO.
Yeah, I knew string can do SSO, so I assumed std::vector was allowed to as well.
> How are you guaranteeing this? C++ doesn't, so what is going on behind the scenes to make this work in Rust?
Rust's lifetimes.
I believe this is incorrect and small-vector optimization is in fact disallowed by the C++ standard. In any case, standard or not, no implementation is ever going to do SVO because it would require a branch on every access, making them incompatible with SIMD instructions.
>Rust also has a concept of zero sized types, which C++ does not
No, C++ allows this optimization too, it just requires that the "sizeof" operator always returns an integer >= 1.
Does C++ really not guarantee this? If yes then I probably have written quite a bit of "broken" code, which assumes that element addresses stay constant during moves of the container.
std::vector<A> vec1; // And initialize it
A* a = &vec1[0];
auto vec2 = std::move(vec1);
A* b = &vec2[0];
assert(a == b);