-
Notifications
You must be signed in to change notification settings - Fork 12.8k
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
Return-position impl Trait is not satisfied by divergent/never type #113875
Comments
The fallback for unsolved type variables is |
According to the RFC |
This is interesting and surprising to me. In any case, my user-level bug report can be summarized as, "I want to |
Regarding your main point, personally I'm not qualified enough to answer or to speak for anybody on the Rust team, so take the following with a grain of salt. To explain a bit what's happening, for a type It's unclear to me if the above could break the language in any major way. It sure feels fishy. (*) If we assume that such a synthesized implementation still needs to follow normal rules and if in such impl associated types are set to trait Main { type P: Auxiliary<Q = u64>; }
trait Auxiliary { type Q; } In a hypothetical different language that allows ⊥ (bottom) to inhabit a trait implementation, things would be easier but such a language would be quite different (esp. in regards to runtime behavior). In any case, somebody with more background knowledge probably has a more well-grounded answer than me. |
Here's an example of why this can't be supported in general: trait Foo {
const ASSOC: i32;
}
fn print_assoc<T: Foo>(_: fn() -> T) {
println!("{}", <T as Foo>::ASSOC);
}
fn get_foo() -> impl Foo {
loop {}
}
fn main() {
// What number should this print?
print_assoc(get_foo);
} |
Well, as I've described above Edit: Of course, that would be horrible for |
@fmease is correct, and I don't think this should be classified as a bug. Unfortunately, |
@Jules-Bertholet: Thanks for the clear and concise example. I see that
which seems clearly related. I also note that, even if we did that, I guess there is more lurking behind this |
If we assume
I think I understand where you are coming from. In a dynamically typed or in an “untyped” (unityped) language, it's possible to just diverge inside the function body showing complete disregard for the (dynamic) return type. In a statically typed language on the other hand, every single (unevaluated) expression and value has to be of a certain type. This means we have to be able to assign a type to Even if we start out with This explains why I was mostly talking about @rustbot label -needs-triage |
Should we nominate this issue for a T-lang or T-types discussion to settle this? |
Joining the discussion, as I have raised a duplicate. My use case was almost exactly the same - write up an fn, and mark it unimplemented / Todo. This was actually a trait implementation that I have not yet finished. The error message was a big wtf for me as well, with the same where's () coming from, but the other confusing issue is that adding a dummy value after the panic invocation makes the compiler bark about unreachable code. So what is the expected way to write an fn that returns an impl T, but is still unimplemented!() ? |
Also the most confusing is that while
|
These are not equivalent. Argument-position impl Trait is sugar for a type parameter, return-position impl Trait is something different. |
Perhaps a silly question, but is it possible to have the trait bound checker pretend /// If `T` is `!`, always return `true`. Otherwise do normal trait bounds checking.
fn type_satisfies_bounds<T>(bounds: &TraitBounds) -> bool {
type_is_never::<T>() || normal_type_satisfies_bounds::<T>(bounds)
} This sidesteps both the question of which traits to implement and, I think, what those traits should have as associated constants/types and type parameters because it pretends to implement any bounds needed of it. Though it also implements self-contradictory bounds like |
A proposition like For the following code the compiler would need to be able to generate machine code: trait Trait { fn transmute<'a>(_: &'a str) -> &'static str; }
fn accept<'a, T: Trait>(input: &'a str) -> &'static str { T::transmute(input) }
fn main() { accept::<!>(&String::new()); } And as I've mentioned above, we could of course introduce a new kind of witness, let's call it Never, where all associated constants and functions would trait Trait { type A: Aux<X = u32> + Aux<Y = u64>; }
trait Aux { type X; type Y; } Okay, so I couldn't come up with a code snippet that would demonstrate unsoundness but making any sort of equality constraint satisfiable (via Never) looks super unsound to me. I feel like allowing Footnotes
|
I don't know, but I imagine any code For example in the comment @Jules-Bertholet asks (rhetorically):
It does not print at all. I know this has been discussed already, for example here by @fmease:
But if some unsoundness were introduced, does it matter? Any code near that unsoundness will never be executed. I know, ugly, but sound. Right? |
You don't need to execute unreachable code to dispatch on methods that would need to be implemented for fn never() -> Option<impl Foo> {
// This is never run, but it means that `impl Foo := !` above.
if false {
let never: Option<!> = Some(loop {});
return never;
}
None
}
trait Foo {
fn from_option(this: Option<Self>) where Self: Sized;
}
fn main() {
Foo::from_option(never());
} |
As a quick-and-dirty workaround (after all we're just trying to see whether the structure of the code compiles), I'm now using:
This circumvents the problem described by @Jules-Bertholet, by providing "evidence" (as @fmease put it). Nevertheless, it would be nice to have some kind of support for this, because "just stubbing a function with How about this: Modify the compilation error to be more helpful, like so? Old:
Suggestion:
|
I tried this code:
I expected to see this happen:
Module should compile without error. If
foo
orbar
are invoked, theyshould diverge. In particular,
foo
andbar
should be equivalent interms of both static and dynamic semantics.
In general,
fn foo() -> T { loop {} }
should compile for any typeT
,because
loop {}
should be at type!
and!
should be a subtype ofevery type. This seems to be the case for all proper types that I've
tried, including uninhabited ones like
std::convert::Infallible
, butnot when
T
is actually a return-positionimpl Trait
.Instead, this happened:
This is surprising, because I'm not sure where the unit type is coming
from. If I replace
impl Iterator<Item = u32>
withimpl Default
, oranother trait that
()
does implement, then it compiles without error.But if I use
impl Trait
for a trait that()
does not implement, thenit raises this compile-time error.
The fact that
bar
compiles makes it feel especially weird thatfoo
does not compile, because they should be equivalent. Yes, there's more
type information (I've used
std::iter::Empty<u32>
as a specific typeimplementing
Iterator<Item = u32>
), but I don't see why that shouldmatter. The error given by rustc wasn't a "ambiguous type; specify type
annotations" kind of error.
Playground with these examples and a few more:
https://play.rust-lang.org/?version=nightly&mode=debug&edition=2021&gist=e8061b7ffb50afef8b230892d458d26b
Meta
rustc --version --verbose
:Also exists in nightly 2023-07-18.
The text was updated successfully, but these errors were encountered: