5 ms·
I have to disagree with the entire premise of the article. It's the fact that Box isn't unique that gives it this behavior. The author even says as much, but di
by ComputerGuru 2y ago
I have to disagree with the entire premise of the article. It's the fact that Box isn't unique that gives it this behavior. The author even says as much, but dismisses it because it gets in the way of their point:
> While it can be argued that box is like a T, but on the heap, and therefore moving it should invalidate pointers, since moving T definitely has to invalidate pointers to it, this comparison doesn’t make sense to me. While Box<T> usually behaves like a T, it’s just a pointer.
The "it's just a pointer" argument is moot (and pointers have nothing to do with the issue, it's a question of exclusive ownership and nothing more). It's a high-level object (i.e. not a pointer) to which rust's very clear aliasing and single name rules apply. Thou shalt not have multiple live T that point to the same memory address. QED.
It's the same reason this code is unsafe:
struct Foo;
impl Foo {
fn bar(&mut self) { ... }
fn baz(&self) { ... }
}
fn get_foo() -> &mut Foo {
unsafe { static mut FOO_SINGLETON: Foo = Foo; }
unsafe { &mut FOO_SINGLETON }
}
Here we don't even have a pointer, only statically allocated data stored in .TEXT. We don't even create a second top-level instance of Foo, only an &mut reference to it. But it's unsafe because the compiler can't know whether or not it has exclusive access.
(As a refresher, in terms of "strength" of ownership from the most exclusively owned to the least so, it would go T -> &mut T -> &T.)
- Rusky 2y agoThe problem with this, as the article argues in detail, is that it leaves a hole in functionality that people need but offers no clear benefit in return. A movable owning pointer that exposes its pointer-ness in its semantics is a useful tool when you're doing lower-level, sometimes-unsafe stuff with memory layout. The author points out that, because Box does not currently provide this functionality, there are crates in the ecosystem that step in to provide it instead. This middle ground between "raw pointers for everything" and "aliased XOR mutable" is an important thing to support. This might be acceptable if there were some benefit to Box behaving this way. Perhaps if it actually made a difference for the optimizer? The benchmarks the author did seem to suggest otherwise- many mutations wind up going through a reborrowed `&mut T` anyway. Perhaps the conceptual model of "like a T but on the heap" is enough of a benefit? But being more permissive here doesn't change that model for safe code anyway. The author dismissed this for much more concrete reasons than "it gets in the way of their point."
- vlovich123 2y agoI think the better approach would be to standardize some kind of feature that lets you annotate variables as "not noalias" so that Rust knows to elide the noalias for those values. That's a much more generic way to solve the ecosystem problem without changing Box semantics or introducing one off types. That being said, there's already a solution in the ecosystem which is to not use aliased pointers after giving the pointer to Box & moving it.
- simonask 2y agoFor the record, Rust does have a "not noalias" builtin magic type in the standard library: UnsafeCell. https://doc.rust-lang.org/stable/std/cell/struct.UnsafeCell.html https://doc.rust-lang.org/stable/std/cell/struct.UnsafeCell....
- LegionMammal978 2y agoNo, UnsafeCell doesn't change any rules around non-aliasing for &mut/Box. (E.g., if you move a Box<UnsafeCell<T>>, it will still invalidate any pointers to the data.) All it does is change the rules around immutability for & references. &T is "not noalias" regardless of UnsafeCell, but it's immutable in its absence. Meanwhile, there is a magic "not noalias" mechanism currently recognized by Miri, in the form of !Unpin types. But this is considered a temporary hack to keep it from complaining about pinned futures that reference their own fields, in the absence of an actual language feature. Also, it only applies to accessing values through unique references, not to moving or writing to them by their binding.
- Rusky 2y ago&T is noalias - while there may be aliases none can write, which is the important thing - without UnsafeCell, and &UnsafeCell<T> loses noalias for that reason.
- remram 2y ago> It's a high-level object (i.e. not a pointer) I don't know why so many people in this thread try to pretend that there's no reason to see Box as a pointer, no one has ever called it that, and every user drawing a parallel is confused. The documentation for Box is literally (emph mine): >> *A pointer type* that uniquely owns a heap allocation of type T. Until that documentation changes I find the article's point quite valid.
- CodeMage 2y ago> The documentation for Box is literally (emph mine): > >> A pointer type that uniquely owns a heap allocation of type T. > Until that documentation changes I find the article's point quite valid. I think there's some confusion here and that it's because the concept of "pointer" is slightly overloaded. One overload of the meaning is "first-class pointer types": https://doc.rust-lang.org/reference/types/pointer.html https://doc.rust-lang.org/reference/types/pointer.html The other overload, the one used in the docs for Box, Rc, and such, is basically "anything that implements Deref". People who say "Box is not a pointer" are referring to the fact that Box is not a first-class pointer type, i.e. it's neither a reference nor a raw pointer.
- ComputerGuru 2y agoI only meant "not a raw pointer" because rust supports read and write operations on raw pointers with very different aliasing semantics. It is an owned pointer, with emphasis on the "owned". You can have as many raw pointers to the same memory location as you like, you just can't have multiple native rust objects pointing to that same memory alive at once, though. It's also obvious because Box<T> implements Drop, so obviously it's not just something you can pass to a function in lieu of a pointer and if you do pass it to a function, you can no longer make any assumptions about the lifetime or validity of any pointers to the same data.