-
Notifications
You must be signed in to change notification settings - Fork 13k
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
rustc: Allow safe access of shareable static muts #14862
Conversation
now permitted? |
Indeed! |
// run. It could also read the updated value of LOG_LEVEL in which case it | ||
// reads the correct value. | ||
// | ||
// Also note that the log level stored is the real log level minus 1 so the |
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.
s/minus/plus/? (If it's being stored as minus one, why is there an additional - 1
below?)
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.
Ah yes, the comment should be plus, but the minus below is to return the real log level as opposed to the internally stored one. As in, if you specify RUST_LOG=3
, the stored log level will be 4
but any callers of log_level()
should see 3.
r? @pnkfelix |
Ping, r? @pnkfelix |
Is this actually a safe approach? My impression was that I feel like a better approach would be to allow Note that types like However, a |
I think it's safe to say that a Meanwhile a And this also explains why |
It's still not possible to have a program with UB without using It seems perfectly reasonable to regard accessing a |
Entirely irrelevant. The existence of The entire point of |
No it can't. I mean, yes you can write that code, but that would be incorrect code. The point of |
As is overwriting a
No, it's clearly not. We can currently write bug-free code that writes to a |
@huonw It differs because you can write safe wrappers for
Correct, but it's only bug-free because reading is
Incorrect. You can still write a safe wrapper for reading that handles synchronization, yes, but reading without that wrapper is also considered safe. Except it can't be safe. The only way to make reading safe is to go through the wrapper. Again, the mere existence of code that writes to a Instead of accusing me of hyperbole, you should consider that perhaps you're actually wrong about this. I am not being hyperbolic in the slightest. I am 100% serious when I use the phrase "literally impossible". |
Let's please calm down before commenting further. Heated arguments largely make little progress. |
Have you considered code such as this? static mut FOO: uint = 3;
static mut LOCK: NativeMutex = NATIVE_MUTEX_INIT;
fn write() {
unsafe {
let _l = LOCK.lock();
FOO = 4;
}
}
fn read() -> uint {
unsafe {
let _l = LOCK.lock();
FOO
}
} All reads/writes made to This is not the only case where this is safe, there are many many other safe and defined situations. Concurrent reads and writes only invoke undefined behavior if there is at least one unsynchronized operation occurring concurrently with any write operation. Note that this does not include all possible behavior, as it seems you are claiming. One could have externally synchronized reads/writes which allow unsynchronized reads/writes to have defined behavior. Also note that the precise definition of
As implemented, this is precisely allowing exactly that. It is allowed to take a shared reference, Other operations, such as writing to a global, may invoke undefined behavior, so they are |
All existing read/writes in your code are safe, yes. But with your PR, I can trivially write fn foo() -> uint {
FOO
} and this is unsafe. My ability to write unsafe code consisting of nothing but safe operations means that you have violated the contract of the Basically, you are making the same mistake that @huonw is, which is confusing the code you have written with the code that is legal to write. It doesn't matter whether or not you actually write an unsynchronized read, the fact that you can is what makes the Regarding |
You could adjust This seems very similar: if you are wrapping |
@huonw As I said before to @alexcrichton, you are confusing the code you have written with the code that is legal to write. Your function there is buggy, and will cause other This PR is introducing the opposite problem, where it's the safe code that behaves wrong. You may feel like arguing that you can trivially write The problem with this PR is that you cannot fix the |
It occurs to me there's another alternative to making The downside to this approach is that it's still not particularly safe, because you can create a Of course, introducing yet another reference type kind of sucks. So I think allowing |
(wow there has been a lot of heat on this thread since the last review ping...) |
I'm fairly certain @kballard is right here. If you write |
I suppose it is possible to work around that by having your |
(Also I would discourage any attempts at allowing |
This is an interesting debate. I confess I am torn. On the one hand, I agree that the safety requirement is subtle, and hence if you can write to a static mut (unsafely), it is best to say that all reads are also unsafe, since both of them must be synchronized together for the code to be data-race free. On the other hand, it would be very nice to have global locks and atomic integers that one can expose safely. (Of course it is possible to create fn accessors instead.) I wonder if we can skin this cat another way. Currently, we have rules that say: "static variables cannot have |
I'm a fan of "static Unsafe vars must be Share and will be placed into mutable memory". I too feel that it's a bit strange, that a static could actually be mutable, but that seems to me to be a consequence of the existence of Unsafe. What we have today, where you can't put Unsafe into a static, has shown to be an undesirable limitation (e.g. because actually-safe constructs end up requiring static mut and unsafe {}) |
I remember this coming up at the last work week, but it has the hazard of making code a little odd: use std::sync::atomics;
let i = atomics::INIT_ATOMIC_UINT.fetch_add(1, atomics::SeqCst);
let j = atomics::INIT_ATOMIC_UINT; // assuming #13233
println!("{}", j.load(atomics::SeqCst)); // prints 1, not 0 |
@alexcrichton ah yes, that was the thing we didn't like. |
I pondered this some more. It is certainly true that one must use privacy when writing to static muts, or else you cannot create a safe interface (since you must control reads and writes). However, this is almost always true when writing unsafe code. In fact, that's kind of the whole point of the unsafe keyword-- is to indicate code that is not safe on its own because it may interact with other code, so it is your responsibility to know what interacts exist and control them. It seems clearly worse to me to be able to modify the |
Also, the supposed problem concerning static muts already exists for the If we wanted to make static mut more secure, one option would be to simply forbid writes and mutable borrows of static mut values. In that case, if you wanted "free" writes (rather than using |
@nikomatsakis We could introduce |
@kballard yes, I consider three types of static to be overkill and inconsistent. |
That's true. The problem is we have safe interfaces for values that want to live in statics (such as I do agree that 3 kinds of statics is not ideal, but I'm not sure it's worse than what we have today, with the requirement for |
Would a lint that flags In other words, would that satisfy the requirement that there be a fixed set of code (that must then be reasoned about in isolation), rather than allowing arbitrary new accessors from other mod's to be mixed in? |
Or an alternative take on my previous suggestion: Make reads of |
You can't get rid of the unsafe {}. Rust has never adopted a policy of unsafe {} only being required for public things. And it doesn't make any sense to start; with a static mut that doesn't require unsafe {} you can violate memory safety with supposedly-safe code. |
I suppose my suggestion of flagging I worry that there is some fundamental philosophical divide here that strikes me as analogous to the debate that raged on rust-lang/rfcs#80 , and for some reason that divide is leading to very strongly worded comments from both sides on this ticket. |
@pnkfelix I agree that this is analagous -- which seems to offer precedent for the approach we planned in the RFC. (At some later point, perhaps, we could add a notion of an |
@kballard Your objection about safe code being able to violate the invariants of unsafe code is already true today. For example, consider an example like this: mod M {
use std::ty::Unsafe;
pub struct A<T> {
a: Unsafe<T>,
// Other fields.
}
impl<T> A<T> {
// Functions that provide a safe interface.
}
} I can add a new safe function to the module and most likely break invariants preserved by the existing code: mod M {
use std::ty::Unsafe;
struct A<T> {
a: Unsafe<T>,
// Other fields.
}
impl<T> A<T> {
// Functions that provide a safe interface.
fn backdoor<'a>(&'a self) -> &'a T {
&self.a.value
}
}
} I think it would be interesting to pursue a model where adding safe code doesn't break invariants provided by unsafe code, but it's not what we have today. |
I have created an RFC for this PR: rust-lang/rfcs#177 |
@zwarich Your code snippet has actually convinced me that the fact that the |
Taking a shareable loan of a `static mut` is safe if the type contained in the location is ascribes to `Share`. This commit removes the need to have an `unsafe` block in these situations. Unsafe blocks are still required to write to or take mutable loans out of statics. An example of code that no longer requires unsafe code is: use std::sync::atomics; static mut CNT: atomics::AtomicUint = atomics::INIT_ATOMIC_UINT; let cnt = CNT.fetch_add(1, atomics::SeqCst); As a consequence of this change, this code is not permitted: static mut FOO: uint = 30; println!("{}", FOO); The type `uint` is `Share`, and the static only has a shareable loan taken out on it, so the access does not require an unsafe block. Writes to the static are still unsafe, however, as they can invoke undefined behavior if not properly synchronized. Closes rust-lang#13232
As I have recently learned from aturon, it is undefined behavior to have any unsynchronized access exected in parallel with an unsynchronized write. This sort of behavior was possibly invoked by liblog during initialization. The log level constant has been altered to become an atomic variable which prevents undefined behavior.
Closing pending the resolution of rust-lang/rfcs#177 |
Taking a shareable loan of a
static mut
is safe if the type contained in thelocation is ascribes to
Share
. This commit removes the need to have anunsafe
block in these situations.Unsafe blocks are still required to write to or take mutable loans out of
statics. An example of code that no longer requires unsafe code is:
As a consequence of this change, this code is now permitted:
The type
uint
isShare
, and the static only has a shareable loan taken outon it, so the access does not require an unsafe block. Writes to the static are
still unsafe, however, as they can invoke undefined behavior if not properly
synchronized.
Closes #13232