You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Decision on "must define before use" for opaque types #117866
Nominating for T-lang to decide whether the following rule should hold for opaque types:
If the body of an item that may define the hidden type of some opaque does define that hidden type, it must do so syntactically before using the opaque type in a non-defining way.
This restriction is called "must define before use."
Consider:
use core::convert::identity;structI;structIShow;implI{fnshow(&self) -> IShow{IShow}}structOnIShow;traitOnI{fnshow(&self) -> OnIShow{OnIShow}}implOnIforI{}fntest(n:bool) -> implOnI{lettrue = n else{loop{}};let x = test(!n);//~ NOTE this is the opaque typelet _:OnIShow = x.show();//~ NOTE this is a non-defining use//~^ ERROR if the body registers a hidden type for the opaque, it// must do so *before* using it opaquelylet _:IShow = identity::<I>(x).show();//~^ NOTE this registers a hidden type for the opaque, but does so// too lateloop{}}fnmain(){}
Because the code above works today, this would be a breaking change for RPIT.
In further discussion, @compiler-errors has described that he's not worried about this and that there are likely ways that we could implement the simple forms of passthrough that one might want -- if we wanted them -- without the restriction proposed here. If he's not worried about it, then neither am I. So removing the nomination and closing.
So "before" here is meant in the sense of "syntactically before the other one in a function"? Or "before" in a data-flow sense? E.g., in if ... { a } else { b }, is a "before" b or not?
Rust usually doesn't care much for syntactic "before", for things like which items I can reference and for type inference. So what is the motivation for imposing such a requirement on opaque types?
before in a "walk the hir during hir typeck" sense 😁
the reason why this requirement would be desirable is that it allows us to simplify the handling of opaque types, especially wrt the new solver. We did a crater run emulating this change in #120798. This only impacts places in HIR typeck where we need to eagerly make a decision, i.e. cannot defer it by e.g. emitting an obligation instead.
While there are other places which would be impacted here, the main change is its impact on method resultion: expr.method() eagerly selects which method to use, emitting an error if its ambiguous. If the type of expr is an opaque type in its defining scope we currently resolve method by using the "item bounds" of the opaque.
Ideally opaques are handled similar to other aliases (e.g. associated types) and eagerly normalized, changing the type of expr to be an inference variable (and we remember that the opaque normalizes to that inference variable). This breaks the following example and also resulted in multiple failures found in the crater run:
use std::future::Future;use futures::FutureExt;fngo(i:usize) -> implFuture<Output = ()> + Send + 'static{asyncmove{if i != 0{// This returns `impl Future<Output = ()>` in its defining scope,// we don't know the concrete type of that opaque at this point.// Currently treats the opaque as a known type and succeeds, but// from the perspective of "easiest to soundly implement", it would// be good for this to be ambiguous.go(i - 1).boxed().await;}}}
Given that 1) this pattern seems generally desirable to support and 2) not supporting it is a breaking change, I believe we should accept the additional complexity required to support this with the new solver and not break this pattern.
Hm... I see. I have to say I find that go example quite magical and wouldn't expect it to typecheck -- there's nothing actually determining the return type here, is there?
But really my main concern is ossifying implementation details of current rustc, and leaking these details into the spec. If we have a notion of "before" that becomes relevant for typeck, we should carefully document/specify how "before" is defined, because it becomes part of our stable language surface.
If we have a notion of "before" that becomes relevant for typeck, we should carefully document/specify how "before" is defined, because it becomes part of our stable language surface.
we already have this notion rn:
fnmain(){letmut x = Default::default();
x.sort();// type annotations neededdrop::<Vec<i32>>(x);}
there's nothing actually determining the return type here, is there?
there is: it returns an async move block. That blocks awaits a Box<dyn Future>. It returns a coroutine where all types used at await-points are fully known
Given that we found other paths forward and given the discussion above, this doesn't seem ripe for lang discussion at the moment, so let's unnominate for now.
If we have a notion of "before" that becomes relevant for typeck, we should carefully document/specify how "before" is defined, because it becomes part of our stable language surface.
we already have this notion rn:
fnmain(){letmut x = Default::default();
x.sort();// type annotations neededdrop::<Vec<i32>>(x);}
I've been wondering this before. I think this isn't generally well-known limitation of the type inference, people often claim that the inference works both ways and are surprised when I show them this example.
Is this documented somewhere? Does it has a name that would help searching releated issues?
Activity
traviscross commentedon Jan 8, 2024
@rustbot labels -I-lang-nominated
In further discussion, @compiler-errors has described that he's not worried about this and that there are likely ways that we could implement the simple forms of passthrough that one might want -- if we wanted them -- without the restriction proposed here. If he's not worried about it, then neither am I. So removing the nomination and closing.
traviscross commentedon Feb 7, 2024
@rustbot labels +I-lang-nominated
Reopening as @lcnr has now proposed a similar rule.
(There is some relation here to #120549.)
[-]TAIT decision on "must define before use"[/-][+]Decision on "must define before use" for opaque types[/+]RalfJung commentedon Feb 15, 2024
So "before" here is meant in the sense of "syntactically before the other one in a function"? Or "before" in a data-flow sense? E.g., in
if ... { a } else { b }, isa"before"bor not?Rust usually doesn't care much for syntactic "before", for things like which items I can reference and for type inference. So what is the motivation for imposing such a requirement on opaque types?
lcnr commentedon Feb 15, 2024
before in a "walk the hir during hir typeck" sense 😁
the reason why this requirement would be desirable is that it allows us to simplify the handling of opaque types, especially wrt the new solver. We did a crater run emulating this change in #120798. This only impacts places in HIR typeck where we need to eagerly make a decision, i.e. cannot defer it by e.g. emitting an obligation instead.
While there are other places which would be impacted here, the main change is its impact on method resultion:
expr.method()eagerly selects which method to use, emitting an error if its ambiguous. If the type ofexpris an opaque type in its defining scope we currently resolvemethodby using the "item bounds" of the opaque.Ideally opaques are handled similar to other aliases (e.g. associated types) and eagerly normalized, changing the type of
exprto be an inference variable (and we remember that the opaque normalizes to that inference variable). This breaks the following example and also resulted in multiple failures found in the crater run:Changing this example to error with ambiguity breaks existing crates and this pattern also seems generally desirable, e.g. https://github.com/kurnevsky/esxtool/blob/88538ca7c8e068b5e38d4b386eb1bf3d5cede3d0/src/mwscript/parser.rs#L186-L199 would require using
Parser::parse_stream(minusplus(), i)instead.Given that 1) this pattern seems generally desirable to support and 2) not supporting it is a breaking change, I believe we should accept the additional complexity required to support this with the new solver and not break this pattern.
RalfJung commentedon Feb 15, 2024
Hm... I see. I have to say I find that
goexample quite magical and wouldn't expect it to typecheck -- there's nothing actually determining the return type here, is there?But really my main concern is ossifying implementation details of current rustc, and leaking these details into the spec. If we have a notion of "before" that becomes relevant for typeck, we should carefully document/specify how "before" is defined, because it becomes part of our stable language surface.
lcnr commentedon Feb 15, 2024
we already have this notion rn:
there is: it returns an
async moveblock. That blocks awaits aBox<dyn Future>. It returns a coroutine where all types used at await-points are fully knowntraviscross commentedon Mar 20, 2024
@rustbot labels -I-lang-nominated
Given that we found other paths forward and given the discussion above, this doesn't seem ripe for lang discussion at the moment, so let's unnominate for now.
golddranks commentedon Sep 8, 2024
I've been wondering this before. I think this isn't generally well-known limitation of the type inference, people often claim that the inference works both ways and are surprised when I show them this example.
Is this documented somewhere? Does it has a name that would help searching releated issues?
Ref: Just talked about this on Reddit some weeks ago: https://www.reddit.com/r/rust/comments/1eou6re/comment/lielbao/?utm_source=share&utm_medium=mweb3x&utm_name=mweb3xcss&utm_term=1&utm_content=share_button
lcnr commentedon Sep 9, 2024
I think not. We don't have a name or a documented list of places during type inference which are eager, big ones are: