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: Overconstraining and omitting `unsafe` in impls of `unsafe` trait methods #2316
Conversation
Centril
added some commits
Jan 30, 2018
Centril
added
the
T-lang
label
Jan 30, 2018
sfackler
reviewed
Jan 30, 2018
| method is called on a specific type where the method is known to be safe, | ||
| that call does not require an `unsafe` block. | ||
|
|
||
| # Motivation |
This comment has been minimized.
This comment has been minimized.
sfackler
Jan 30, 2018
Member
Do you have some real world examples of traits and implementations that would take advantage of this?
This comment has been minimized.
This comment has been minimized.
Centril
Jan 31, 2018
Author
Contributor
Here's one example: https://doc.rust-lang.org/std/slice/trait.SliceIndex.html#tymethod.get_unchecked
with impls:
- https://doc.rust-lang.org/src/core/slice/mod.rs.html#967-999
- https://doc.rust-lang.org/src/core/str/mod.rs.html#1827-1853
While this falls under "trivial example", avoiding unsafe where possible is still nice =)
...stay tuned for more
This comment has been minimized.
This comment has been minimized.
sfackler
Jan 31, 2018
Member
Does there exist any code that has ever called slice.get_unchecked_mut(..)?
This comment has been minimized.
This comment has been minimized.
Centril
Jan 31, 2018
Author
Contributor
Oh; you were referring to the calling side of things and not the motivation, I see. then slice.get_unchecked_mut(..) does not apply (I think). My bad ;)
This comment has been minimized.
This comment has been minimized.
sfackler
Jan 31, 2018
Member
I am trying to figure out what problem that currently exists this RFC is proposing to solve. "If this feature existed it would enable me to make a thing that was previously impossible", or "If this feature existed I could make this thing I wrote better".
This comment has been minimized.
This comment has been minimized.
cramertj
Feb 12, 2018
Member
I think I agree with you that specialization is a better long-term solution.
This comment has been minimized.
This comment has been minimized.
sfackler
Feb 12, 2018
•
Member
Why is adding a new unstable language feature a shorter term solution than using another unstable language feature?
This comment has been minimized.
This comment has been minimized.
cramertj
Feb 12, 2018
Member
@sfackler Perhaps I'm using it wrong, but I don't believe the current version of specialization allows this specific impl. The following errors with "conflicting implementations of trait ImmovableFuture for type MyFutureCombinator<_>":
#![feature(specialization)]
trait Future {}
trait ImmovableFuture {}
default impl<T> ImmovableFuture for T where T: Future {}
struct MyFutureCombinator<T>(T);
impl<T> Future for MyFutureCombinator<T> where T: Future { }
impl<T> ImmovableFuture for MyFutureCombinator<T> where T: ImmovableFuture { }
This comment has been minimized.
This comment has been minimized.
cramertj
Feb 12, 2018
Member
Either way, I'd expect this feature to be much quicker and easier to implement and stabilize than even the most minimal version of specialization.
This comment has been minimized.
This comment has been minimized.
sfackler
Feb 12, 2018
Member
Then this can provide some motivation for finishing specialization! IIRC that kind of implementation was covered in at least some revisions of the specialization RFC.
Are there any more motivating examples for this feature? I am kind of concerned with adding a new language feature that will be used for a single trait.
This comment has been minimized.
This comment has been minimized.
|
@nikomatsakis Didn't we used to have bugs because of this? |
This comment has been minimized.
This comment has been minimized.
I remember something like this, but can't find where the change was done. |
This comment has been minimized.
This comment has been minimized.
|
What is the story on APIs that currently use |
This comment has been minimized.
This comment has been minimized.
burdges
commented
Jan 31, 2018
|
Isn't the point of
In particular, you might want safe blocks inside an Also, if you really need an
|
This comment has been minimized.
This comment has been minimized.
Lokathor
commented
Jan 31, 2018
|
Hopefully we can get the inverse as well, though I would understand if that was a separate RFC. |
This comment has been minimized.
This comment has been minimized.
Lokathor
commented
Jan 31, 2018
|
@burdges If you're already writing an unsafe function then having to put an unsafe block inside of it is silly to say the least. |
This comment has been minimized.
This comment has been minimized.
|
The
Searching for "trusted iterator length" problem points to https://internals.rust-lang.org/t/the-trusted-iterator-length-problem/1302, where one of the solutions is about I think "unsafe to implement" stuff should be performed through |
This comment has been minimized.
This comment has been minimized.
Actually, I would like to dig a bit deeper here. If I have a trait: trait FooTrait {
unsafe fn foo();
}The
I guess this RFC is proposing that it does not mean the former and may or may not mean the latter... Fundamentally, I don't think it even makes sense to declare an abstract fn I agree with @kennytm: I think this will answer one of the questions I had when reading the RFC: what should the rustdocs for |
This comment has been minimized.
This comment has been minimized.
|
@mark-i-m I wrote a blog post about what I believe is the correct way to interpret unsafe in this context: https://boats.gitlab.io/blog/post/2018-01-04-unsafe-abstractions/
There's a subtle but important distinction that I think you're missing: being marked unsafe doesn't mean you can do any unsafe anything in that function. An So its not at all tied to having a default impl; the point is that every impl has to rely on the same invariants that the trait declares. |
This comment has been minimized.
This comment has been minimized.
Lokathor
commented
Jan 31, 2018
|
So, could we summarize that whole thing with "Any time |
This comment has been minimized.
This comment has been minimized.
|
@Lokathor Yes, that's exactly how I interpret it. |
This comment has been minimized.
This comment has been minimized.
I think this is the right interpretation, though in the past I've had another POV (roughly: "unsafe means read the comments; somebody has an extra job here"). To be honest I think we should revisit the design of unsafe to help us make this clearer (who is imposing what obligations on whom), but I'm not 100% sure yet what I think. |
This comment has been minimized.
This comment has been minimized.
|
Hmm... @withoutboats's blog post was very thought provoking! It bothered me that the use of My proposal (hopefully this doesn't have too many holes):
For an explanation of how I came to this definition of unsafe see the following long-winded details... Details
I write all of this because I think that if we are to move forward with a feature like this RFC, we need to nail down the definition of |
This comment has been minimized.
This comment has been minimized.
Lokathor
commented
Feb 1, 2018
|
That's, like, way complicated my friend. Let's try going back to the simple version and building from there. Also, let's try to avoid introducing any new special terms.
|
This comment has been minimized.
This comment has been minimized.
|
cc @eternaleye wrt. |
This comment has been minimized.
This comment has been minimized.
eternaleye
commented
Feb 1, 2018
•
|
Copying a portion of my message elsewhere:
I negated the definitions of some of these specifically to make them effects: Something that grants the body/callee additional powers, while constraining the user/caller. Rust has a rudimentary effect system, but discussing it is complicated by several things:
EDIT: With these reframings in place, I'd argue that:
It would also seem to argue that:
|
This comment has been minimized.
This comment has been minimized.
|
@Lokathor The question is who does
I argue that
|
This comment has been minimized.
This comment has been minimized.
eternaleye
commented
Feb 1, 2018
|
@mark-i-m That's not what masking means. Masking is stopping the outward (resp. inward) propagation of an effect (resp. restriction) marker.
|
This comment has been minimized.
This comment has been minimized.
|
@eternaleye I think we are talking about the same thing. "stopping the outward propagation of an effect" is isomorphic to promising that you fulfill all preconditions. Not fulfilling all preconditions forces you to assume a precondition, which someone else must fulfill (which is propagation)... |
This comment has been minimized.
This comment has been minimized.
eternaleye
commented
Feb 2, 2018
•
|
@mark-i-m: Mm, I disagree. I'm going to show by comparison with So, with trait Foo {
// Here, in the trait-side signature
unsafe fn foo();
}
impl Foo for () {
// Here, in the impl-side signature
unsafe fn foo() {
// Here, in the impl-side body
}
}It's clear that In addition, Now, let's look at trait Foo {
// Here, in the trait-side signature
const fn foo();
}
impl Foo for () {
// Here, in the impl-side signature
const fn foo() {
// Here, in the impl-side body
}
}Here, the infection moves inwards - if it appears in the trait signature, it must appear in the impl signature, and if it appears in the impl signature, one must only use Now for // Here, in the trait-side signature
unsafe trait Foo {
fn foo();
}
// Here, in the impl-side signature
unsafe impl<B: Bar> Foo for () {
fn foo() {
// Here, in the impl-side body
<B as Bar>::bar()
}
}Note how, compared to However, the impl-side body calls This is where Anyway, in none of these examples does masking occur between trait-side signature and impl-side signature - in both cases where it's possible, it occurs between impl-side signature and impl-side body; explicitly in one case, and implicitly in the other. EDIT: Note that this actually affects |
This comment has been minimized.
This comment has been minimized.
Lokathor
commented
Feb 2, 2018
Perhaps I was somehow unclear in my explanation?
That's not a proposal by me for some future version of rust, that's a statement of the current rules of rust as it exists today. Personally, I can't even envision another way for it to work. When you say "the user, rather than the implementor", I don't know what you mean. If you impl |
This comment has been minimized.
This comment has been minimized.
|
@eternaleye Thanks for the clarification :) I think I don't quite follow you here:
But the My goal was to come up with a (proposed) general formulation so we can see what we is possible in the future and consistent with current rust. I think the formulation I came up with does that, and I still believe it is consistent with both your/@withoutboats's model and @eternaleye's... but I seem to be doing a bad job of explaining it
In the case of a |
This comment has been minimized.
This comment has been minimized.
eternaleye
commented
Feb 2, 2018
•
|
@mark-i-m This calls back to the first post I made in this thread - specifically, the quoted part:
In particular, However, there is no way for Considering By contrast, if This problem is further magnified if instead of |
burdges
referenced this pull request
Feb 2, 2018
Closed
Adding unsafe modules and unsafe blocks outside functions. #2148
This comment has been minimized.
This comment has been minimized.
burdges
commented
Feb 2, 2018
|
I still do not understand why |
This comment has been minimized.
This comment has been minimized.
eternaleye
commented
Feb 2, 2018
•
|
@burdges Firstly, static mut foo: u32 = 0;
fn safe1() {
println!("Just starting");
}
// The signature must have `unsafe` because...
unsafe fn not_safe(x: u32) {
// ... the body uses `unsafe` and infected the signature
foo += x;
}
fn safe2() {
println!("Almost done");
}
// The signature must have `unsafe` because...
unsafe fn not_safe2() {
// ... the body uses `unsafe` and infected the signature
foo -= 1;
}
// The signature isn't infected because...
fn example() {
// ...safe functions do not infect...
safe1();
// ...and masking stops the spread...
unsafe {
// ...of any infection present.
not_safe(1);
}
safe2();
unsafe {
not_safe2();
}
}EDIT: We lack a "block-scoped effectless context" construct, but calling a safe Note that infection refers to the marker - the keyword |
This comment has been minimized.
This comment has been minimized.
|
@eternaleye Ah, that's a great example! Under my model,
|
This comment has been minimized.
This comment has been minimized.
eternaleye
commented
Feb 2, 2018
•
|
@mark-i-m: And my point all along has been that your model doesn't seem to match the consensus, and the way you arrive at your model is unsound as well. I have consistently seen Weakening an invariant to a precondition/postcondition pair is entirely unsound - in fact, Solidity making this mistake was the root cause of a severe contract bug in Ethereum (theDAO). Also, even your new API still falls afoul of what I'm talking about, and also fails to meet the reason trait TrustedLen: Iterator { // not `unsafe`
fn size_hint(&self) -> usize; // also not `unsafe`
}However, because it is not an unsafe trait TrustedLen: Iterator { // "`unsafe` code may rely on this"
fn size_hint(&self) -> usize; // not `unsafe`
}Cool, now let's implement it. unsafe impl TrustedLen: Iterator { // "`unsafe` code may rely on this"
fn size_hint(&self) -> usize { // not `unsafe`
<Self as Iterator>::size_hint(self).0
}
}Oh, hm. This code that The problem, @mark-i-m, is that Rust already has the
|
This comment has been minimized.
This comment has been minimized.
|
This conversation seems to have gone far astray from this RFC proposal; I'd encourage y'all to continue the conversation somewhere like an internals thread. |
This comment has been minimized.
This comment has been minimized.
burdges
commented
Feb 2, 2018
|
I'm maybe miss-using "infectious" but.. I'm worried foremost about
Right now, we cannot Afaik, the same mistake exists for non-trait As to I once suggested another scheme would be "trait tests" in which |
This comment has been minimized.
This comment has been minimized.
Lokathor
commented
Feb 2, 2018
Of course it benefits from safety? Unsafe doesn't travel inward, only outward.
This is actually one of the first things the Rustonomicon tells you that you must never do. |
This comment has been minimized.
This comment has been minimized.
|
@withoutboats Thanks for the moderator note @eternaleye @withoutboats I created the following internals thread to continue discussion: https://internals.rust-lang.org/t/what-does-unsafe-mean/6696 |
This comment has been minimized.
This comment has been minimized.
Servo is full of such code. For example I'm currently working on a mechanism where a lot of types must never be encountered on the stack. These values are produced unsafely through an unsafe trait with an unsafe method, and other things using that trait have to follow various invariants and handle the value produced unsafely with care. This trait has no default implementation of this method. |
This comment has been minimized.
This comment has been minimized.
|
Yeah, we discussed a lot more on the thread I linked above. I think I have better understanding now |
aturon
assigned
cramertj
Mar 15, 2018
This comment has been minimized.
This comment has been minimized.
|
Hopefully we can work with the "const" effect in addition to the "safe" one. For example, it would be nice to make |
This comment has been minimized.
This comment has been minimized.
eternaleye
commented
Mar 20, 2018
•
|
@scottmcm As noted in an earlier message, RFC 2237 is for that specific issue (the second element of the RFC, to be precise):
|
Centril
referenced this pull request
Apr 3, 2018
Open
RFC: Overconstraining and omitting `unsafe` in impls of `unsafe` trait methods #8
This comment has been minimized.
This comment has been minimized.
|
I think it may be worth considering a middle ground to start, where impls are allowed to drop In any case, I think that if they were to do so, then any specializations would also be required to be safe. That is, you must narrow the things you specialize. That seems readily implementable. |
This comment has been minimized.
This comment has been minimized.
|
I also think we need to find a better phrasing for documentation purposes than "over-constraining" which I frankly did a horrible number on. |
This comment has been minimized.
This comment has been minimized.
eternaleye
commented
Apr 27, 2018
•
|
Oh, a bikeshed!
Personally, I think "frugal impls" works best, including for other markers - |
This comment has been minimized.
This comment has been minimized.
|
What is the motivation for this feature at this point? Self-referential futures are now dealt with via |
This comment has been minimized.
This comment has been minimized.
|
@eternaleye I raise you one "economic impls" @sfackler I defer to @cramertj who now champions this RFC and has some use cases (I also think @joshtriplett had some...?). |
This comment has been minimized.
This comment has been minimized.
|
I've run into a few cases where I wanted to implement a trait and didn't need the qualifiers on the trait in my implementation, so this seems reasonable to me. |
This comment has been minimized.
This comment has been minimized.
This would be more like a subset of #2063 then. |
This comment has been minimized.
This comment has been minimized.
|
@petrochenkov I think @nikomatsakis was suggesting that in that case the implementer would not be able to do |
Centril commentedJan 30, 2018
Rendered
This RFC allows safe implementations of
unsafetrait methods. Animplmay implement a trait with methods marked asunsafewithout marking the methods in theimplasunsafe. This is referred to asoverconstraining the method in the
impl. When the trait'sunsafemethod is called on a specific type where the method is known to be safe, that call does not require anunsafeblock.A simple example:
This RFC was written in collaboration with @cramertj whom it was my pleasure to work with.
cc @withoutboats @aturon