-
Notifications
You must be signed in to change notification settings - Fork 13.3k
Pin is unsound due to the literal constructor #139013
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
Comments
I don't think the
This sounds like a huge limitation and breaking change. Afaik the Maybe "unsafe fields" as a language feature would be a better way to fix this theoretical unsoundness, but again, IMHO this is hardly an issue in practice |
The following code compiles with the nightly Rust compiler. #![feature(unsafe_pin_internals)]
#![allow(unused)]
use std::pin::Pin;
use std::marker::PhantomPinned;
struct Data {
_pinned: PhantomPinned,
}
impl Data {
fn new() -> Pin<Box<Self>> {
Box::pin(Data {
_pinned: PhantomPinned,
})
}
}
fn main() {
let mut obj = Data::new();
//let mut p = Pin::new(obj); //This is not allowed in safe Rust because of the type of filed _pinned is PhantomPinned.
let mut p = Pin { __pointer: &mut obj }; // But we can bypass the check through the literal constructor.
}
I strongly doubt that this is a serious (proper) way to handle the issue.
Yes, I agree that there is little chance of developers using it incorrectly. But soundness is a key competitive advantage of Rust over other languages, isn't it? I believe there should be a better way to support the required features without undermining the soundness of safe Rust. |
I don't think your example is safe Rust because it has the “unsafe” feature (Although I think it it better to have lints such as internal_features and unsafe_code triggered against unsafe_pin_internals feature to make that more clear (which may have already been done).) |
Oh, yes. I hadn't noticed that there is a keyword Even without the unsafe feature, we can bypass the safety check through the macro use std::pin::Pin;
use std::pin::pin;
use std::marker::PhantomPinned;
struct Data {
_pinned: PhantomPinned,
}
impl Data {
fn new() -> Pin<Box<Self>> {
Box::pin(Data {
_pinned: PhantomPinned,
})
}
}
fn main() {
let mut obj = Data::new();
//let mut p = Pin::new(obj); //This is not allowed in safe Rust because of the type of filed _pinned is PhantomPinned.
//let mut p = Pin { __pointer: &mut obj }; // But we can bypass the check through the literal constructor. This line of code requires enabling the feature of unsafe_pin_internals.
let p = pin!(obj); // Now we can bypass the check without any unsafe features.
} |
pin macro in your example is fine. That macro is implemented in a sound way called stack pinning. See the documentation: https://doc.rust-lang.org/nightly/core/pin/macro.pin.html |
Can you explain in more detail why it is sound? Thanks! |
It takes ownership of the value, creates a temporary which inaccessible from the user, and creates a pinned reference from a reference to it. Unless the value implements Unpin, the user cannot obtain the normal mutable reference or ownership to the value without the unsafe code. See the comment in pin macro for the complete explanation of what pin macro does: Lines 1948 to 2017 in ecb170a
|
Is it not obvious that a feature named "unsafe pin internals" is not intended for consumers that aren't, well, pin itself? |
That said the macro implementation will likely change the future anyways to make this no longer necessary. Until then there will be this public field hack, but that doesn't make it any less sound in practice. |
Yes, it is obvious, but it derives from Rust's initial design, specifically the five types of unsafe code described in the book. If some of these features are unsafe, I think a better approach might be something like @taiki-e Thanks for you explanation. My concern here is not the possibility to move the object, but the consistency of design with respect to Unpin and PhantomPinned. Besides, I have a question about the use std::pin::Pin;
use std::marker::PhantomPinned;
struct Data {
_pinned: PhantomPinned,
}
//impl Unpin for Data {}
impl Data {
fn new() -> Pin<Box<Self>> {
Box::pin(Data {
_pinned: PhantomPinned,
})
}
}
fn main() {
let mut obj = Data::new();
//let mut p = Pin::new(obj); //This is not allowed without impl Unpin for Data {}
let mut p = unsafe { Pin::new_unchecked(obj) } ;
} |
You are free to mark any structure with a Where the fact that |
There's no tracking issue for the field, which means it's not stabilization-path, so you shouldn't be using it. Not to mention that there's already a comment about this in the code: Lines 1090 to 1103 in 2a06022
There's lots of nightly features that are unsound -- most famously #31844 -- so I think this is just not anything worth doing about right now. It's not on a stabilization track to expose this, so personally I'd just close the issue. At most, we could "fix" this by adding a stronger warning if you enable a feature without a tracking issue. |
@scottmcm Thanks for the feedback. We are reviewing the safety of the entire std-lib, which is why we raised this concern. I believe the community needs a place to track all temporary solutions that could compromise the soundness of safe Rust before they escalate to an uncontrollable scale. I’m okay with closing the issue. |
The safe constructor
Pin::new(pointer: Ptr)
restricts the pointed object with theUnpin
trait. However, this restriction can be bypassed using the literal constructor ofPin
or thepin!
macro.Why not prevent this by making the literal constructor private and modifying the
pin!
macro as follows?To the best of my knowledge, the side effect is that API users can no longer create a
Pin
object usingpin!
if the object does not implementUnpin
. However, developers can still usePin::new_unchecked(pointer: Ptr)
to achieve the same result. More importantly, I believe unsoundness must not be tolerated in safe Rust.The text was updated successfully, but these errors were encountered: