-
Notifications
You must be signed in to change notification settings - Fork 1.6k
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
Unsafe statics #2937
base: master
Are you sure you want to change the base?
Unsafe statics #2937
Conversation
One thing I think is interesting: if we were to advance the unsafe references idea ever as well, we would end up in a state where unsafe feels a lot more elucidated. Many kinds of items would have an "unsafe" variant, which in some cases relaxes some of the requirements (eg unsafe statics do not need to be sync, unsafe references do not need to point to valid data) and in other cases (unsafe functions and traits) does not. But all introduce points of abstraction at which invariants are introduced to be maintained by use sites (which must be inside unsafe scopes). This would normalize unsafe Rust compared to today. Right now the "unsafe superset" is a hodgepodge of items which are not conceptually well-unified with the safe subset (raw pointers, static muts). |
Except unsafe blocks and |
Yea, exactly, thats what I meant by "unsafe scopes." But maybe you're just refering to the fact that we use the same keyword for both sides of this contract, which - yea, whether that was a good idea or not a separate question. 😅 |
# Summary | ||
[summary]: #summary | ||
|
||
Replace static muts with unsafe statics, which are error prone. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Editorial note: this sounds like you are saying that unsafe statics are error prone.
creating new invariant abstraction points. It's unclear what use cases this would have, but it seems | ||
like something which could be compelling. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh I see, so that would be using an unsafe static
for "communication" between a library and its environment, and make that communication subject to extra constraints? Yes that sounds potentially interesting indeed.
(I struggled at first to understand this paragraph, it would help if you said explicitly that a hypothetical crate would make such an unsafe static
part of its public API.)
declaration point of the static which does not obey the safe static rules unsafe. It is a more | ||
direct expression of user intent than creating a RacyUnsafeCell: you want to create a static that | ||
cannot be proven by the compiler to uphold the requirements of a safe static, so you create an | ||
unsafe static. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In particular, unsafe static
is somewhat more direct because it attaches the safety comment to the value (well, place) of concern, whereas RacyUnsafeCell
would have a safety comment at its unsafe impl
that basically says something like "the only instance of this type is a static
and it is being used as follows". The latter feels like an awkward indirection.
I'd intuitively expect |
The In this RFC,
perhaps this causes the surprises in the above comments. Is it possible to split the two roles? Here is an alternative:
This means the following is allowed (perhaps with a lint)... static X: Cell<i32> = Cell::new(0); but doing these would require unsafe: // not ok:
let x = &X;
// ok:
let x = unsafe { &X };
// not ok:
let x = X.get();
// ok:
let x = unsafe { X.get() };
// not ok:
X.set(10);
// ok:
unsafe { X.set(10) };
// not ok:
let x = &mut X;
// never ok:
let x = unsafe { &mut X };
// not ok even if Y is Copy (??) (does `static Y: *mut T = ...` make sense?):
let y = Y;
// ok if Y is Copy (??):
let y = unsafe { Y };
One big drawback of such proposal is that the |
I like the idea of a static that requires unsafe to access, but I don't like that it also implicitly removes the I'm opposed to statics in general allowing non |
Since before 1.0, Rust has had a feature called `static mut`, which allows users to define statics | ||
to which mutable references can be taken. Actually taking a mutable reference is unsafe. However, | ||
this feature is incredibly footgunny and almost impossible to use correctly, since creating a | ||
mutable reference to shared memory is undefined behavior, even if you never dereference that |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When you say 'shared memory', do you mean 'memory that already has a immutable/mutable reference to it'? The current wording makes it sound as though creating any mutable reference is undefined behavior, since a static is always 'shared' by virtue of having global scope.
I share the semantic questions raised in other comments about whether the keyword Could the RFC please document the expectation for whether code generation for (Some of this has been discussed on other issues, but I'd like to see it captured in the RFC.) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems good to me.
I just read over the comments. I agree that having the "unsafe static is not Sync" stood out to me on first read as a bit of a surprise, though I overlooked it; it might be nice if the I found @kennytm's idea intriguing, but I think I would prefer for the "opt-out" to be an explicit mechanism. As a general rule, I'd prefer to avoid the compiler having to "figure out" if a type if Sync to know whether a mechanism is unsafe -- I'd rather we structure any trait obligations in terms of requirements (this must be true or the program errors) rather than tests (if this is true, one thing happens, but otherwise another), particularly when it comes to "inter-procedural" (in this case, inter-item) requirements. I think the question of whether this use of (As a side note, if/when we do adopt some notion of safety and const fn, there would also be a potential interaction with |
This analogy doesn't really work. Non- |
Yes. I guess the point is that an "ordinary" safe static is sort of analogous to function like // can call from multiple threads because we require `T: Sync`
fn foo<T: Sync> {
unsafe { ... }
} whereas the unsafe variant might be more like // can't call from multiple threads
unsafe fn foo<T> { } In any case, I think it remains true that the |
So is there a reason we're not calling them |
I think there would be two reasons:
|
I don't think this feature is needed or desirable. As fair as I see it, the only thing that's currently impossible with However, all of this can be solved without adding yet another language feature, analogous to Add a type pub struct UnsafeSyncCell<T>(ManuallyDrop<T>);
unsafe impl<T> Sync for UnsafeSyncCell<T> { }
impl<T> UnsafeSyncCell<T> {
/// # Safety
///
/// Must guarantee that this is only called from the correct thread.
pub unsafe fn get(&self) -> &T { &self.0 }
/// # Safety
///
/// See above.
pub unsafe fn into_inner(self) -> T { ManuallyDrop::into_inner(self.0) }
} Since this is mainly intended to be used in statics, I think it's okay that the value is not dropped automatically. If someone wants to use this outside of statics, they would need to write their own drop glue. On top of that, one could build other types with safe interfaces, for example one that stores the thread id when the type is constructed and checks it on every access. Such a type could even drop the value if the thread is the correct one and abort otherwise. There may be other issues with this besides drop that I'm not seeing right now, but I don't think there's any hard blockers. Adding such a library type would allow deprecating (and eventually removing) |
@brain0 That alternative is already under discussion in rust-lang/rust#53639, and this is a counter-proposal designed to avoid some of its problems. |
I'm sorry, I did not know that. One argument in the other thread was that such a rare use case does not justify adding such types to std. I think it's the opposite, and adding a language feature for something that can be easily solved in a library is not justified. |
I liked @dtolnay's minimal alternative: rust-lang/rust#53639 (comment) which will look like just a small special case feature (and removal of The drawback of special-casing |
Add unsafe statics, which are like statics except they are unsafe to declare and use, and they are not required to implement Sync. Unlike static muts, they do not allow mutable references.
cc @RalfJung
If it turns out this is more complex or disputed than I initially imagine, I may not be able to steward this to merge. But it seems like a really straightforward revision of static muts to make them no longer a misfeature.
Rendered