Join GitHub today
GitHub is home to over 31 million developers working together to host and review code, manage projects, and build software together.
Sign upRFC for unsafe blocks in unsafe fn #2585
Conversation
RalfJung
referenced this pull request
Nov 4, 2018
Closed
Tracking issue for unsafe operations in const fn #55607
This comment has been minimized.
This comment has been minimized.
|
If this were to be accepted, it would be much better to get the warning in before the 2018 epoch hits stable. If there's no time to get it in for 2018 then I don't think it should be accepted. |
oli-obk
reviewed
Nov 4, 2018
| # Drawbacks | ||
| [drawbacks]: #drawbacks | ||
|
|
||
| This new warning will likely fire for the vast majority of `unsafe fn` out there. |
This comment has been minimized.
This comment has been minimized.
oli-obk
Nov 4, 2018
•
Contributor
We can start as allow by default. The fact that const unsafe fn already behaves this way and that clippy can uplift this lint to warn, will already make sure to migrate large parts of the ecosystem.
Oh... you are already mentioning that below in the unresolved questions...
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
|
@Diggsey There will be another edition. And once we no longer warn about |
This comment has been minimized.
This comment has been minimized.
burdges
commented
Nov 4, 2018
|
We needed this years ago, so the sooner the better. |
Ixrec
reviewed
Nov 4, 2018
| # Prior art | ||
| [prior-art]: #prior-art | ||
|
|
||
| None that I am aware of: Other languages do not have `unsafe` blocks. |
This comment has been minimized.
This comment has been minimized.
Ixrec
Nov 4, 2018
Contributor
C# has unsafe blocks in addition to unsafe methods. Though it's not super helpful since I'm not aware of the C# community ever discussing burden of proof issues like this RFC does, probably because 99.99% of the time the answer in that language is "unsafe just isn't worth it". I couldn't even find a C# style guide that mentions the existence of unsafe code, much less has guidelines for making it less dangerous.
This comment has been minimized.
This comment has been minimized.
Centril
Nov 4, 2018
Contributor
Furthermore, while other languages may not have blocks many do have escape hatches (which are relevant to this section)... Examples I found:
-
Haskell: http://hackage.haskell.org/package/base-4.12.0.0/docs/System-IO-Unsafe.html#v:unsafePerformIO and http://hackage.haskell.org/package/base-4.8.0.0/docs/Unsafe-Coerce.html
-
Idris: https://github.com/idris-lang/Idris-dev/blob/master/libs/prelude/Builtins.idr#L189
-
Agda: https://agda.readthedocs.io/en/v2.5.4.2/language/postulates.html
-
Coq: https://github.com/coq/coq/wiki/CoqAndAxioms also, http://adam.chlipala.net/cpdt/html/Universes.html#lab78
-
ReasonML: https://reasonml.github.io/docs/en/interop -- actually, this is sort of a block...
-
OCaml: https://caml.inria.fr/pub/docs/manual-ocaml/libref/Obj.html
-
Ada: http://www.adaic.org/resources/add_content/docs/95style/html/sec_5/5-9-1.html
This comment has been minimized.
This comment has been minimized.
RalfJung
Nov 5, 2018
Author
Member
@Centril But this RFC is specifically about blocks and nesting of unsafe escape hatches. I do not think any of the examples you mention apply there, do they?
@Ixrec Thanks, I had no idea! And it looks like unsafe operations can be used freely in unsafe functions. :/
This comment has been minimized.
This comment has been minimized.
Centril
Nov 5, 2018
Contributor
@RalfJung not with that specificity no; the languages have the "block" form, e.g.:
x = unsafePerformIO $ do
foo
bar
...what they lack is the unsafe function form.
This comment has been minimized.
This comment has been minimized.
RalfJung
Nov 5, 2018
Author
Member
That's still quite different from Rust. It's just a normal function to the compiler, no checks for "unsafe operations" or so are performed. I do not see a close enough relation to this RFC.
This comment has been minimized.
This comment has been minimized.
Centril
Nov 5, 2018
Contributor
@RalfJung alright; fair enough. Let's leave this bit (the comment thread) open for interested readers who want to see the associated material linked. :)
Centril
added
T-lang
T-dev-tools
labels
Nov 4, 2018
Centril
reviewed
Nov 4, 2018
| [drawbacks]: #drawbacks | ||
|
|
||
| This new warning will likely fire for the vast majority of `unsafe fn` out there. | ||
|
|
This comment has been minimized.
This comment has been minimized.
Centril
Nov 4, 2018
Contributor
Other possible drawbacks to list:
-
It will become less ergonomic to write unsafe code (it's justified I think, but worth mentioning...).
-
People might just do this:
unsafe fn frobnicate(x: T, y: U, ...) -> R {
unsafe {
... // Actual code.
}
}and then nothing has been gained. I don't know what the risk of this is, but worth mentioning.
This comment has been minimized.
This comment has been minimized.
RalfJung
Nov 5, 2018
Author
Member
I added (a variant of) 1.
For 2., I think something has been gained: It is not possible to incrementally improve this function's unsafe locality. Or maybe it is not worth it, then that has at least been explicitly documented in the code.
This comment has been minimized.
This comment has been minimized.
Centril
Nov 5, 2018
Contributor
Yeah I'm not entirely sure 2. is a drawback or not; I usually try to write the section as what someone else might think is a potential drawback (but not necessarily me) -- i.e. this is the section where I try to bring out my inner Dr. Phil / empathy =P
This comment has been minimized.
This comment has been minimized.
RalfJung
Nov 7, 2018
Author
Member
The drawbacks section now says
Many
unsafe fnare actually rather short (no more than 3 lines) and will
likely end up just being one largeunsafeblock. This change would make such functions less ergonomic to write.
That should cover 2, right?
This comment has been minimized.
This comment has been minimized.
Centril
Nov 7, 2018
Contributor
@RalfJung the concern is actually slightly different here; it is that people will just go and write one big unsafe { ... } when they shouldn't.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
Centril
Nov 8, 2018
Contributor
@mark-i-m yes; sure -- the concern is that the change we do here might not have any noticable effect cause people could be lazy and...
This comment has been minimized.
This comment has been minimized.
|
I've added T-dev-tools for the possible clippy lint for now. If no such clippy lint is proposed in the final version before final review of the RFC I'll remove that team. |
This comment has been minimized.
This comment has been minimized.
|
I think this RFC need more examples of realistic code where this would help, and an explanation of why it helps in enough cases that it's worse the obvious pain in current cases. That seems especially true since "safe" code is just as suspect when it's around Another alternative: an More generally, |
This comment has been minimized.
This comment has been minimized.
|
@scottmcm There are some places in the language where you are forced to use Here are some example from a project I worked on:
|
This comment has been minimized.
This comment has been minimized.
|
Another concrete use case I find valuable: Callback functions used when interacting with C libraries are almost always |
This comment has been minimized.
This comment has been minimized.
There is some syntactical similarity between |
This comment has been minimized.
This comment has been minimized.
well... not everyone agrees (as you know) ;) https://internals.rust-lang.org/t/what-does-unsafe-mean/6696/2 |
This comment has been minimized.
This comment has been minimized.
|
Some further examples of longer
Basically any time your However, I also have to admit that the vast majority of |
This comment has been minimized.
This comment has been minimized.
I am aware. However, I gave my usual arguments above, and AFAIK they have not been refuted yet, so I will keep claiming that everyone who says I think whoever claims that But anyway, this is getting off-topic. ;) |
RalfJung
added some commits
Nov 5, 2018
This comment has been minimized.
This comment has been minimized.
|
I’m worried about the migration of existing code. I’d like to see this RFC make it a requirement that But this is tricky, simply wrapping the entire body of a function into a new |
This comment has been minimized.
This comment has been minimized.
I wouldn't necessarily say so. It still provides benefits for new unsafe code written later, and it permits gradual migration of existing unsafe code. |
This comment has been minimized.
This comment has been minimized.
|
Good point, my "sort of" only applies to existing code. I didn’t meant to argue against this RFC, I was only pondering the merits of different ways to deal with the migration. Sorry if I implied otherwise. |
This comment has been minimized.
This comment has been minimized.
|
It's okay. :) I will add something about migrations to the RFC text. |
This comment has been minimized.
This comment has been minimized.
newpavlov
commented
Nov 5, 2018
|
I wonder how many of existing unsafe fn foo() {
unsafe {
// ..
}
}And while treating |
This comment has been minimized.
This comment has been minimized.
|
@mark-i-m I didn't intend to suggest that we should set that precedent or provide such a macro. I just wanted to point out that if in some application domains typing |
This comment has been minimized.
This comment has been minimized.
Lokathor
commented
Dec 19, 2018
•
|
I'm with @RalfJung The function body is one line. "If it ever executes unsafe code", yes of course that's a bug! That's why you don't go adding random other lines into any place in any module that ever uses unsafe code. unsafe code is definitionally unverifiable by the rules of rust alone. It has to be analyzed by a human. That means that we want to make the job of the human as easy as possible. Right now, we have things like this: pub unsafe fn set_len(&mut self, new_len: usize){
self.len = new_len;
}And your claim is that "it's all safe operations, so you should write a safe internal function to do it and then call that safe internal function"? No. That's wrong. Please read the Rustonomicon again. One unsafe block anywhere in a module should be assumed to taint the entire module. The line What we want is the polar opposite: an RFC where you can mark a field as being an |
This comment has been minimized.
This comment has been minimized.
No, I never claimed that. It should be an
Maybe |
This comment has been minimized.
This comment has been minimized.
Lokathor
commented
Dec 19, 2018
|
Oh, hold on, my deepest apologies @RalfJung, it was @burdges who wanted the safe internal function split up. Still, if we agree that the whole module is tainted, wouldn't you also agree that assuming that we can reduce the unsafety by making the scope of the unsafe blocks smaller is largely a misleading notion? |
This comment has been minimized.
This comment has been minimized.
eternaleye
commented
Dec 19, 2018
•
|
@Lokathor You're conflating two distinct concerns. Making the bodies safe by default is not about reducing the scope of The whole module is tainted, yes... but only if there is an obligation that is neither discharged nor deferred to the module's user. |
This comment has been minimized.
This comment has been minimized.
|
To add to what @eternaleye said: The point of Quoting from the first motivational paragraph of this RFC:
Your arguments go besides the point here. |
This comment has been minimized.
This comment has been minimized.
burdges
commented
Dec 19, 2018
•
|
There are numerous examples linked up thread: There are five In other word, If you read this code, all your time gets spent flipping over to |
This comment has been minimized.
This comment has been minimized.
Lokathor
commented
Dec 19, 2018
•
|
People keep linking me to #2585 (comment) but I've got a thumbs up on it so I do not know why you think I have not seen it. I'd be more convinced if unsafe code had a way of classifying the dangers at any given moment and masking out some of them but not all of them was possible, so then the compiler can tell you want you missed. Like checked exceptions in other languages. As it stands, the idea that you're proving some things but not others doesn't really line up with how an unsafe block works. |
This comment has been minimized.
This comment has been minimized.
eternaleye
commented
Dec 19, 2018
•
|
@Lokathor Again, you're conflating two things. The annotation However, separating the two makes it much, much more feasible to improve that in the future - including through using structured comments parsed with external tooling, proc macros, or other means.
EDIT: Notably, there exist proof obligations that are checked by the compiler. This is what a type system is. |
This comment has been minimized.
This comment has been minimized.
|
I've personally never seen code that accidentally did unsafe things in an |
This comment has been minimized.
This comment has been minimized.
|
I've read a lot of unsafe code, and I often find it non-trivial to look out for accidental unsafe operations. |
This comment has been minimized.
This comment has been minimized.
Lokathor
commented
Dec 19, 2018
|
I'm not conflating anything that doesn't already go together. The caller having a proof burden despite you using an unsafe block obviously means that you didn't really discharge all of the proof burden involved. That's why I don't like it. If you were enhancing unsafe so that it was something like: unsafe (alignment, ptr_location, valid_bits) {
self.ptr.read()
}And you could discharge some proofs but not all, and then the rest would float up to the caller, that would be amazing and fruitful. I'd support that in a heartbeat. |
This comment has been minimized.
This comment has been minimized.
|
@Lokathor that's more of an "effect" model of unsafety, and I think that'd be really cool! |
This comment has been minimized.
This comment has been minimized.
|
+1 to @RalfJung's comment about reading vs. writing-- I personally wouldn't find this that big of a deal for code that I write myself, but it'd be really helpful when reviewing others code. (I have, on occasion, however, found bugs in others' code that resulted from "unaware-unsafe") |
This comment has been minimized.
This comment has been minimized.
eternaleye
commented
Dec 19, 2018
•
This is a misunderstanding both of "proof burden" and of "discharge". Discharging a proof burden does not mean that you show it is always true no matter what. Discharging a proof burden means that you show it is implied by things that are already believed to be true in context.
For example, consider:
It is clearly an unsafe function (it has UB if called with a value outside
|
This comment has been minimized.
This comment has been minimized.
burdges
commented
Dec 19, 2018
|
We improved tiny-keccek's performance by replacing some unsafe code with safe code, especially removing one |
This comment has been minimized.
This comment has been minimized.
|
@burdges Now that's weird. Can you point to the concrete change that lead to a performance improvemenet?
|
This comment has been minimized.
This comment has been minimized.
burdges
commented
Dec 20, 2018
•
|
In that case, it's presumably merely that Rust's optimizations expect safe coding styles, but Niko suggests that some future optimizations might only work when far enough away from unsafe code. Anyways, the original PR is debris/tiny-keccak#8 but the merged version is debris/tiny-keccak#11 Another popped up this year in debris/tiny-keccak#36 but only because they never merged the bounds check eliding from debris/tiny-keccak@ccf709b |
This comment has been minimized.
This comment has been minimized.
|
@burdges Thanks! I doubt it is about the safe vs. unsafe distinction that anything got faster there. Seems more like you also went from slices to arrays, which helps because more is statically known. |
This comment has been minimized.
This comment has been minimized.
graydon
commented
Jan 12, 2019
|
Mixed feelings. This was dealt with at length in the initial design and the argument that you mention here that most unsafe functions are short and this would make them read noisily as |
This comment has been minimized.
This comment has been minimized.
If we could write |
This comment has been minimized.
This comment has been minimized.
pqnet
commented
Feb 9, 2019
•
|
It could be solved the other way around, by introducing a |
This comment has been minimized.
This comment has been minimized.
phil-opp
commented
Feb 9, 2019
|
Regarding the noise: I strongly prefer the noise of unsafe fn foo() {
/// Documentation that explains why this inner function is needed
fn foo_inner() {
// the actual implementation, but with more rightward drift
}
foo_inner()
}This is a lot of boilerplate, especially if the actual implementation is very short. So it's tempting to just use the default unsafe environment, even if it results in a larger |
This comment has been minimized.
This comment has been minimized.
|
It looks like the discussion has largely settled and most of it seems to be about whether this would be a noise improvement or a noise increase. Does that sound right? |
This comment has been minimized.
This comment has been minimized.
burdges
commented
Mar 1, 2019
|
Assuming this RFC passes, then clippy could start warning not merely about unnecessary unsafe, but about unsafe blocks being larger than necessary, although the logic for this sounds delicate. If we did that now then we'd force people into excessive nested |
This comment has been minimized.
This comment has been minimized.
I think there are three questions whose answers build on each other. Question one is whether doing things either way can be useful depending on the project. If the answer is yes, we could just add an allow-by-default lint that raises an error if an Question two is what should the default behavior be. That is, should the above lint be allow or warn by default ? (note that we can't turn this lint to error by default in either of the current editions) Question three is whether that lint should be turned into an error in the next edition. We don't have to resolve all these questions now. I personally think that both the RFC and the discussion show that adding such a lint would be an useful thing to do, and that allows people to choose whatever fits them or their projects best. This would also let us collect experience about how "noisy" such a lint actually is. We could leave what the defaults should be as a question to be resolved in the tracking issue, and require that resolving it should have its own FCP, were a summary comment based on experience with the feature can kickstart the discussion to avoid repeating all the arguments. It only makes sense to discuss whether this lint should error in the next edition iff we decide that it should warn by default in the current ones. It might well be that we recognize that, while this lint is a better default, it isn't the best behavior for some projects, and therefore, it should always be possible to disable it So even if we make it warn by default, that doesn't necessarily mean that it will become an impossible to disable error in future editions. |
This comment has been minimized.
This comment has been minimized.
|
I think this would be an useful lint to have, and I'd like to get more experience with it by using it in my projects and seeing how others use it in theirs before I fully make my mind about whether it should be enabled by default or not. |
This comment has been minimized.
This comment has been minimized.
phil-opp
commented
Mar 1, 2019
|
We would also need to change the
|
RalfJung commentedNov 4, 2018
No longer treat the body of an
unsafe fnas being anunsafeblock. To avoid a breaking change, this is a warning now and may become an error in a future edition.Rendered
Cc @rust-lang/wg-unsafe-code-guidelines