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 upUnsound projection when late-bound-region appears only in return type #32330
Comments
This comment has been minimized.
This comment has been minimized.
|
Just make these lifetimes early-bound. Why would you want a |
This comment has been minimized.
This comment has been minimized.
Right, I have that implemented now and am working out the kinks.
I don't want a binder over every type. I want fn lb<'a>(x: &'a u32) -> &'a u32 { x }then the type of |
This comment has been minimized.
This comment has been minimized.
|
What do you mean by that? Aren't signatures already contained in the fn def type? Types that are not determined by the function parameters need to be "early-bound" (because you need to be able to project out) - because |
nikomatsakis
added a commit
to nikomatsakis/rust
that referenced
this issue
Mar 18, 2016
nikomatsakis
added a commit
to nikomatsakis/rust
that referenced
this issue
Mar 18, 2016
This comment has been minimized.
This comment has been minimized.
|
Note the problem is not specific to bare functions, but can be reproduced with closures, which also currently permit late-bound regions in just the return type: #![feature(unboxed_closures)]
fn foo<'a>() -> &'a u32 { loop { } }
fn bar<T>(t: T, x: T::Output) -> T::Output
where T: FnOnce<()>
{
x
}
fn main() {
let x = &22;
let z: &'static u32 = bar(|| -> &u32 { loop { } }, x); // <-- Effectively casts stack pointer to static pointer
} |
This comment has been minimized.
This comment has been minimized.
|
So implementing this change by making the regions early-bound breaks a few bits of code in rustc. It also breaks parsell. The problem in parsell boils down to the fact that if you bind the type Another example from the compiler is that we have functions like this: fn expand<'cx>(&mut Foo) -> &'cx Result { ... }which are being used in a context that requires The galling thing about both of these examples is that the original code was correctly typed. I think that the latter case, at least, could probably be fixed by improving the code in I am not sure if this fix would scale up to parsell, because of the layers of traits involved, which require exact types. But it might. It's a bit hard to say without giving it a try. |
This comment has been minimized.
This comment has been minimized.
Yes. Very concretely, whereas today all function types have an implicit binder, I am talking about refactoring to pull the binder out. So that instead of having In any case, I'm more concerned about trying to ammeloriate the problems I mentioned in my previous comment, which I think are probably orthogonal to this question. |
nikomatsakis
referenced this issue
Mar 22, 2016
Closed
Panic compiling conrod example on nightly #32411
This comment has been minimized.
This comment has been minimized.
|
So the problem is that |
This comment has been minimized.
This comment has been minimized.
|
Actually, this compiles: fn foo<'a, 'b>(_a: &'a str) -> &'b str where &'b (): Sized { "hello" }
fn main() {
let f: fn(&str) -> &str = foo::<'static>;
}because we special-case |
This comment has been minimized.
This comment has been minimized.
|
@arielb1 In my branch I reworked the interaction with The change I made is basically to be more sensitive to subtyping. Whereas today This has some interesting consequences. For example, |
This comment has been minimized.
This comment has been minimized.
|
I am worried about that consequence - it means that By the way, how is that implied? We have
Don't the edges here go in the wrong direction? I thought the change was about |
This comment has been minimized.
This comment has been minimized.
|
This is interesting. I certainly agree with making the implicit arguments and the universal quantifier (the binder) explicit; it is the approach I am taking in my formalization. Traits and thus associated types are not yet covered by my formalization, and won't be for the next few months, at least. My rough plan, should I ever get there, was to not leave anything implicit -- to make trait resolution explicit. So, when a function has a trait bound, that would actually translate to an additional argument passed to the function, which provides all the trait methods. (Think of this as the vtable passed next to the other arguments rather than in a fat pointer; monomorphization is then merely partial evaluation given that the vtable pointer argument is hard-coded at the call site.) I assume associated types correspond to type-level functions, which I guess means I have to switch the type system to System F_\omega. I will have to read up on how far my vtable approach scales here ;-) struct foo;
impl<'a> foo_FnOnce : FnOnce<()> for foo {
type Output = &'a u32;
...
}This reveals the problem at hand: From your discussion, I take it that Rust independently checks that |
nikomatsakis
added a commit
to nikomatsakis/rust
that referenced
this issue
Mar 24, 2016
nikomatsakis
added a commit
to nikomatsakis/rust
that referenced
this issue
Mar 24, 2016
nikomatsakis
added a commit
to nikomatsakis/rust
that referenced
this issue
Mar 24, 2016
nikomatsakis
added a commit
to nikomatsakis/rust
that referenced
this issue
Mar 24, 2016
nikomatsakis
added a commit
to nikomatsakis/rust
that referenced
this issue
Mar 24, 2016
nikomatsakis
added a commit
to nikomatsakis/rust
that referenced
this issue
Mar 24, 2016
This comment has been minimized.
This comment has been minimized.
|
This comment has been minimized.
This comment has been minimized.
That's because we already discussed the problem (with explicit impls) in rust-lang/rfcs#447.
The compiler assumes that
That would be non-trivial, as the following code is legal (and nothing forces pub struct Holder {
pub data: Option<<for<'a> Iterator<Item=&'a u32> as Iterator>::Item>
}
fn put<'a, 'b>(h: &'a mut Holder, d: Option<&'b u32>) {
h.data = d;
}
fn get<'a, 'b>(h: &'a Holder) -> Option<&'b u32> {
h.data
}
fn main() {
let mut h = Holder {
data: None
};
{
let x = 4;
put(&mut h, Some(&x));
}
println!("{:?}", get(&h));
} |
This comment has been minimized.
This comment has been minimized.
|
BTW Rust already uses "coercion" for a different thing. I would use |
nikomatsakis
added a commit
to nikomatsakis/rust
that referenced
this issue
Mar 25, 2016
nikomatsakis
referenced this issue
Mar 25, 2016
Closed
Issue warnings for late-bound regions in assoc type bindings and output types #32488
This comment has been minimized.
This comment has been minimized.
Yes, that is a consequence.
Well, let's talk it out, make sure I got it right. First, to be clear, I think that in your example the alpha is a variable, whereas So the algorithm begins by replacing
where each edge represents the So this constraint graph kind of shows what I was getting at: you can consider Put another way, consider that each of the two functions can freely call each other: fn foo<'a>(x: &'a u32, y: &'a u32) {
bar(x, y) // OK: 'b and 'c can both be bound to `'a` (or, in fact, the call site)
}
fn bar<'b, 'c>(x: &'b u32, y: &'c u32) {
foo(x, y) // OK: 'a is inferred to the call site
}Note that this is not the case if one of the regions appears in the output type: fn foo<'a>(x: &'a u32, y: &'a u32) -> &'a u32 { bar(x, y) }
fn bar<'b, 'c>(x: &'b u32, y: &'c u32) -> &'b u32 { foo(x, y) } //~ ERROR E0495 |
This comment has been minimized.
This comment has been minimized.
Wait, I am confused here. I can certainly write down traits that, when used as a trait object, the compiler complains about for not being object safe. So, there are object-unsafe traits that even are useful. Are you referring to a different notion of object safety?
Okay. I will probably eventually dig into old discussions to figure out the reasons behind this design decision.
I do not understand the type |
This comment has been minimized.
This comment has been minimized.
You can have an object-unsafe trait (e.g.
Associated type projection is required to be a (partial) function. That's basically the point of associated types, not?
That's because it is an incoherent notion. The |
nikomatsakis
added a commit
to nikomatsakis/rust
that referenced
this issue
Apr 4, 2016
This comment has been minimized.
This comment has been minimized.
I never saw associated types as a partial function; I saw them as additional, type-level data that is extracted from the inferred trait implementation; similar to how value-level data (like associated constants or method implementations) are extracted. If you see it as a function, then what is the domain of that function? As you said yourself, I guess I am rather puzzled that this ( |
This comment has been minimized.
This comment has been minimized.
|
Hmm, reading a bit more closely, I'm actually somewhat surprised that this program type-checks. Perhaps more strangely, this one does as well, which seems wrong: #![feature(unboxed_closures)]
fn main() {
static X: u32 = 3;
fn lifetime_in_ret_type_alone() -> &'static u32 { &X }
fn apply_thunk<T: FnOnce<()>>(t: T, _x: T::Output) { t(); }
let x = 3;
apply_thunk(lifetime_in_ret_type_alone, &x);
} |
This comment has been minimized.
This comment has been minimized.
|
Still my attempt to weaponize fails (the following does not compile): #![feature(unboxed_closures)]
fn main() {
static X: u32 = 3;
fn lifetime_in_ret_type_alone() -> &'static u32 { &X }
fn apply_thunk<T: FnOnce<()>>(t: T, x: T::Output) -> T::Output { x }
let y: &u32;
let x = 3;
y = apply_thunk(lifetime_in_ret_type_alone, &x);
}Ah, I think I see what is happening, actually. So |
This comment has been minimized.
This comment has been minimized.
|
What's the status of this? |
arielb1
added
I-nominated
T-compiler
labels
Nov 15, 2016
nikomatsakis
added a commit
to nikomatsakis/rust
that referenced
this issue
Nov 17, 2016
nikomatsakis
referenced this issue
Nov 17, 2016
Merged
make HR_LIFETIME_IN_ASSOC_TYPE deny-by-default #37843
bors
added a commit
that referenced
this issue
Nov 22, 2016
nikomatsakis
removed
the
I-nominated
label
Dec 1, 2016
This comment has been minimized.
This comment has been minimized.
|
@arielb1 still an issue on latest nightly. https://is.gd/I1QNWn (thanks to @niconii) https://is.gd/RpOnJm (shortened) |
stjepang
added a commit
to stjepang/rust
that referenced
this issue
Dec 16, 2016
nikomatsakis
self-assigned this
Dec 22, 2016
This comment has been minimized.
This comment has been minimized.
|
Working on doing a complete fix for this now. |
This comment has been minimized.
This comment has been minimized.
|
@nikomatsakis will this also solve #26325 or #20304? |
nikomatsakis
added a commit
to nikomatsakis/rust
that referenced
this issue
Jan 6, 2017
nikomatsakis
referenced this issue
Jan 6, 2017
Merged
make lifetimes that only appear in return type early-bound #38897
nikomatsakis
added a commit
to nikomatsakis/rust
that referenced
this issue
Jan 25, 2017
nikomatsakis
added a commit
to nikomatsakis/rust
that referenced
this issue
Jan 26, 2017
nikomatsakis
added a commit
to nikomatsakis/rust
that referenced
this issue
Feb 5, 2017
nikomatsakis
added a commit
to nikomatsakis/rust
that referenced
this issue
Feb 5, 2017
nikomatsakis
added a commit
to nikomatsakis/rust
that referenced
this issue
Feb 5, 2017
nikomatsakis
added a commit
to nikomatsakis/rust
that referenced
this issue
Feb 5, 2017
bors
added a commit
that referenced
this issue
Feb 5, 2017
This comment has been minimized.
This comment has been minimized.
|
So I believe this issue is now fixed thanks to #38897. @DemiMarie, in answer to your question (sorry, missed that before): My change does not affect #26325. It does help with #20304 -- but we were already doing some caching. I do have plans to improve the approach much further that I think are enabled by closing this bug. There may also be some amount of simplification I can do, I have to check. |
nikomatsakis commentedMar 18, 2016
While working on a cache for normalization, I came across this problem in the way we currently handle fn items:
That program compiles, but clearly it should not. What's going on is kind of in the grotty details of how we handle a function like:
Currently, that is typed as if it was roughly equivalent to:
However, if you actually tried to write that impl above, you'd get an error:
And the reason is precisely because of this kind of unsoundness. Effectively, the way we handle the function
fooabove allowsOutputto have different lifetimes each time it is projected.I believe this problem is specific to named lifetimes like
'athat only appear in the return type and do not appear in any where clauses. This is because:FnOncetrait's argument parameter. (Assuming it appears in a constrained position; that is, not as an input to an associated type projection.)struct foo<'a>).The current treatment of early- vs late-bound is something that hasn't really scaled up well to the more complex language we have now. It is also central to #25860, for example. I think it is feasible to actually revamp how we handle things in the compiler in a largely backwards compatible way to close this hole -- and the refactoring would even take us closer to HKT. Basically we'd be pulling the
Binderout ofFnSigand object types and moving to be around types in general.However, in the short term, I think we could just reclassify named lifetimes that do not appear in the argument types as being early-bound. I've got an experimental branch doing that right now.
This problem is the root of what caused my projection cache not to work: I was assuming in that code that
T::Outputwould always be the same ifTis the same, and that is not currently the case!cc @rust-lang/lang
cc @arielb1
cc @RalfJung