-
Notifications
You must be signed in to change notification settings - Fork 13.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Aliasing: Does Vec own its contents? #85697
Comments
Is this really a matter of If In the typed arena case the Vec is kept local to the arena, so it is never handed out to code which would be free to exploit Vec's entire API surface. |
This is a very good question. I think that we have to talk about One option would be to simply write that in the documentation. As Ralf suggested, the tools could provide special annotations for this case, for example, Another question would be: how do we call such functions as /// Safety: `v.len() < v.capacity()`
unsafe fn test(v: &mut Vec<u32>, x: &mut u32) {
v.push(7);
*x = 8;
} To sum up, it seems to me that allowing to weaken guarantees given by types brings additional complexity and I am wondering whether that is clearly better than having more, but simpler types, which are much easier to understand. |
We already have that. There are 3 types of unsafe
Note that unsafe traits can entirely consist of safe functions, or no functions at all. But since |
FWIW,
This conflates aliasing requirements and ownership, which are two separate discussions. ;) A Some quotes from #60847 reflecting my position on this question:
|
As mentioned in the other thread, functions like
No, it should not be unsafe. The function is still safe to call for any |
So, regarding this question:
I think some input from @rust-lang/libs could be useful, since this is also an API design question. |
I’m not sure this was ever by intentional design, but for a time (before
Another reason to use |
Thank you, @the8472, for pointing out the relation with unsafe traits! Now this makes much more sense to me.
That is a fair point. However, if we lost automation each time a Nevertheless, if we take @the8472's proposal that aliasing can happen only in well-encapsulated cases and in general safe code it is forbidden, then we are fine because only these tiny number of cases will be affected (by now rereading your arguments, I think that you had the same point, just I did not understand that; I am sorry about that). Most of my comments were based on misunderstanding and can be ignored.
Thanks for mentioning |
Yes, |
Based on @SimonSapin comments it seems that at least now I feel that my main questions were answered, so unless someone wants to continue the discussion, I will close the issue. |
Let me try to summarize my understanding so that in case someone else gets confused about the aliasing of My original question can be rephrased as:
The answer is: yes, by default it can be assumed that the contents of the vector are not aliased. Unsafe code is allowed to weaken this type invariant, but the programmer writing unsafe code must ensure that this weakening is properly encapsulated and observable only to code that promises to handle it. A preferred way for a piece of code to indicate that it allows a weaker precondition (or stronger postcondition) than implied by the types is by implementing the unsafe trait that specifies this weakening. However, if all relevant code is inside a single module/crate (that is the code that weakens the type invariant and the code that handles types with weakened invariants are in the same place) and is ensured to not leak weakened types, then just documenting the code could be sufficient. So, we have a way to weaken an invariant of the type in a well-encapsulated portion of a code. However, do we actually need this additional complexity? In particular, is it necessary to use |
I think "using Vec as an allocator" is misleading, as it is mostly unrelated to this issue. Before // Other alignments left as an exercise to the reader
fn alloc_with_align_1(size: usize) -> *mut u8 {
let mut vec = Vec::with_capacity(size);
let ptr = vec.as_mut_ptr();
mem::forget(vec);
ptr
}
fn dealloc_with_align_1(size: usize, ptr: *mut u8) {
mem::drop(Vec::from_raw_parts(ptr, 0, size))
} As far as I understand there’s nothing wrong with this code. What’s special and potentially dubious about the
|
If fact to implement an arena allocator without #![feature(maybe_uninit_extra)]
type Block<T> = Box<std::cell::UnsafeCell<[std::mem::MaybeUninit<T>]>>;
pub struct Arena<T> {
current_block: Block<T>,
current_block_len: std::cell::Cell<usize>,
// ...
}
impl<T> Arena<T> {
pub fn alloc(&self, value: T) -> &mut T {
let slice = unsafe { &mut *self.current_block.get() };
let next = self.current_block_len.get();
if let Some(slot) = slice.get_mut(next) {
self.current_block_len.set(next + 1);
slot.write(value)
} else {
// Allocate the next block etc.
unimplemented!()
}
}
} … which looks a lot like #60847 since |
FWIW, I don't think this is a general recommendation. Traits should be used when abstraction over the underlying type is required. But I don't think it makes sense to introduce a trait just to specify the weaker precondition. I'd just document it properly in the functions doc-comment. |
Yes I think it does, right here: |
Sorry, for the late response, I was travelling. Reopening the issue since it seems the discussion is not over yet. By the way, if you think that this discussion (or part of it) should be moved somewhere else, please let me know.
As far as I know, this code is correct and still used in many places. I personally do not like this
I think we are talking about the same thing just in different words. What I am saying is that such uses of
Yes, you are right.
@SimonSapin May I ask you why would you prefer to use
It also feels a bit strange for me to use a trait in this case. However, writing just a doc-comment feels too weak for me. To have a concrete example, let's say we have two crates: Crate /// This function appends an element to a vector without creating
/// aliases to already existing elements.
pub fn append(v: &mut Vec<u32>) {
// ...
} Crate // ...
// SAFETY: This call is safe because `append` does not create
// aliases to already existing elements in `v`.
unsafe {
append(v);
}
// ... The reason why I do not feel comfortable about such code is that if |
Please ignore that part. I used it because when
When writing a custom container library, in some ways |
Thanks! This is very useful to know. |
FWIW, the recommended way to do that is to use
I see nothing wrong with that. The same happens, for example, when |
Not so long time ago, I looked into a random sample of
A fair point. |
(Opening a new issue to continue the discussion started in #60847.)
The core of the question is whether I can assume that
x
andv
are disjoint (x
is not pointing into one of thev
's elements):I see at least two reasons why
x
should not be allowed to alias an element ofv
:Vec
is, this would likely kill most of the benefits for static analysis that Rust has against languages like C. The reason for this is that a sound tool (a static analyser or a verifier) would need to track whether any reference could potentially alias some element of a vector, which typically requires quantifiers. Unfortunately, using quantifiers typically leads to reasoning becoming undecidable. Not sure whether it is possible to somehow avoid the undecidability in this particular case, but scalability and performance will certainly be a huge problem.At least one reason why aliasing should be allowed is given in the issue 60847. If I am not mistaken, that issue was motivated by this code in typed arena, which uses
Vec
as a memory allocator. A variation of the code in the issue allows creating a reference that aliases the contents of the vector (playground):My personal (highly biased) point of view is that it would be cleaner to separate the two use cases (vector as a collection of things and vector as a memory allocator) because it would allow the majority of
Vec
users to not worry about potentially problematic aliasing. However, this would require having a new data structure that can be used as an allocator; maybe,RawVec
could be adopted for this?If it is decided that the aliasing should not be allowed, then I have two additional questions:
Box
also give the same guarantee?Vec
or a property of theUnique
pointer that is used insideVec
?cc @RalfJung @matklad
The text was updated successfully, but these errors were encountered: