Skip to content
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

todo! and unimplemented! does not work with impl Trait return types #69882

Open
neckbosov opened this issue Mar 10, 2020 · 20 comments
Open

todo! and unimplemented! does not work with impl Trait return types #69882

neckbosov opened this issue Mar 10, 2020 · 20 comments
Labels
A-diagnostics Area: Messages for errors, warnings, and lints A-impl-trait Area: `impl Trait`. Universally / existentially quantified anonymous types with static dispatch. C-enhancement Category: An issue proposing an enhancement or a PR with one. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.

Comments

@neckbosov
Copy link

This code

trait SomeTrait {
    fn some_func();
}

fn todo_impl_trait() -> impl SomeTrait { todo!() }

does not compile because
the trait SomeTrait is not implemented for ()
But such code

trait SomeTrait {
    fn some_func();
}

fn todo_impl_trait<T: SomeTrait>() -> T { todo!() }

compiles correctly. Can this problem be resolved to use both todo!() and impl Trait?

@jonas-schievink
Copy link
Contributor

This is expected behavior since todo!() is not constrained to any type, so it is inferred to (). In the second case it is constrained to T, which is provided by the caller.

@robinmoussu
Copy link

Given that 4 bugs have been opened in such a short period of time for the same reason, I don't think that a wontfix, works as intented is the right approach.

At minimum an alternative should be provided in the help message from the compiler, but I personnaly think that todo!(), unimplemented!() and the like should typecheck to ! (never), that itsel should typecheck to any impl Trait.

@jyn514
Copy link
Member

jyn514 commented Jul 17, 2020

todo!(), unimplemented!() and the like should typecheck to ! (never), that itsel should typecheck to any impl Trait.

// FIXME: Currently the `everybody_loops` transformation is not applied to:
// * `const fn`, due to issue #43636 that `loop` is not supported for const evaluation. We are
// waiting for miri to fix that.
// * `impl Trait`, due to issue #43869 that functions returning impl Trait cannot be diverging.
// Solving this may require `!` to implement every trait, which relies on the an even more
// ambitious form of the closed RFC #1637. See also [#34511].
//
// [#34511]: https://github.com/rust-lang/rust/issues/34511#issuecomment-322340401

@jonas-schievink
Copy link
Contributor

Reopening this to have a canonical issue to direct people to. This is still working as expected for the moment though.

@jonas-schievink jonas-schievink added A-impl-trait Area: `impl Trait`. Universally / existentially quantified anonymous types with static dispatch. C-feature-request Category: A feature request, i.e: not implemented / a PR. T-lang Relevant to the language team, which will review and decide on the PR/issue. labels Jul 23, 2020
@jonas-schievink
Copy link
Contributor

Actually, let's classify this as a diagnostics enhancement

@jonas-schievink jonas-schievink added A-diagnostics Area: Messages for errors, warnings, and lints C-enhancement Category: An issue proposing an enhancement or a PR with one. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. and removed C-feature-request Category: A feature request, i.e: not implemented / a PR. T-lang Relevant to the language team, which will review and decide on the PR/issue. labels Jul 23, 2020
@jeffs
Copy link

jeffs commented Nov 4, 2020

Is there any suggested work-around for this? It sure seems like the bottom type ought (by definition) to type-check as anything, but as a practical matter, is there any placeholder at all that can be used as a return value from an unimplemented function whose signature demands an impl trait?

@jyn514
Copy link
Member

jyn514 commented Nov 4, 2020

@jeffs in the general case, no, because there could be no type that implements the trait: https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=beb24c95bf6be2a8b48f6381c2b7ad6d

@birkenfeld
Copy link
Contributor

@jeffs in the general case, no, because there could be no type that implements the trait: play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=beb24c95bf6be2a8b48f6381c2b7ad6d

I don't get what the example shows; you can add impl Trait for u8 or something, and still get the same error.

@ede1998
Copy link

ede1998 commented Oct 3, 2021

I don't get what the example shows; you can add impl Trait for u8 or something, and still get the same error.

It shows that it's impossible to return a placeholder type because there might not be a type that impls the trait.

@tavianator
Copy link
Contributor

rustc could make up a type that implements the trait with unreachable!() as the body for every fn.

@ede1998
Copy link

ede1998 commented Nov 13, 2021

No that wouldn't be enough. The trait might also have associated types or constants, which would have to be implemented as well.
Also the trait could be unsafe, so implementing it with unreachable might introduced undefined behavior.

@DCNick3
Copy link

DCNick3 commented Mar 26, 2022

Error message could definitely be improved. Currently this code:

trait Trait {}

fn magic() -> impl Trait {
    panic!()
}

Results in

error[E0277]: the trait bound `(): Trait` is not satisfied
 --> src/lib.rs:3:15  
  |
3 | fn magic() -> impl Trait {
  |               ^^^^^^^^^^ the trait `Trait` is not implemented for `()`

Which is really confusing.

Using Vec<impl Trait> gives a message that is much better (at least saying that it has anything to do with the ! type and the return value):

error[E0720]: cannot resolve opaque type
 --> src/lib.rs:3:19
  |
3 | fn magic() -> Vec<impl Trait> {
  |                   ^^^^^^^^^^ cannot resolve opaque type
4 |     panic!()
  |     -------- this returned value is of `!` type
  |
  = help: this error will resolve once the item's body returns a concrete type

Why is it not shown in the first case?

@oli-obk
Copy link
Contributor

oli-obk commented Mar 26, 2022

I want to fix this. Unfortunately if you had an impl Trait for () {} in your first snippet, that would compile right now and error if my fix would get accepted.

I could try to just fix the diagnostic, but it feels like just piling on hacks (the fact that this works if () implements the trait is already a huge hack imo)

My preferred solution would be to just always error like in the vec case

@oli-obk
Copy link
Contributor

oli-obk commented Mar 26, 2022

No that wouldn't be enough. The trait might also have associated types or constants, which would have to be implemented as well. Also the trait could be unsafe, so implementing it with unreachable might introduced undefined behavior.

A concrete example that would be unsound or at least ICE the compiler is https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=ea0e808536f4373dc7be9f002159fa1d

If we just didn't register any hidden type, the make_me call cannot be compiled successfully.

If we used ! fallback, we would get an error because ! does not implement the trait. Of course we could say we generate the trait and panic in all methods, but what happens to associated types and constants? Do we invent random values?

This gets worse when we'll have type alias impl trait, as then you can directly name the opaque type and access associated items directly without jumping through hoops like in my playground.

We could special case todo and unimplemented to just pick some type that implements the trait, but that seems like a real footgun and we should totally lint those cases imo

@drdozer
Copy link

drdozer commented Oct 7, 2024

I just hit this. It makes it impossible to stub out anything that returns impl T. So even if you don't want to write it now, you have to, because ... reasons. Is there any way this could be partially supported for things where it won't break the world? So the compiler would have the option to say "can't do this because trait Foo has evil construct xxx"?

@oli-obk
Copy link
Contributor

oli-obk commented Oct 7, 2024

If you know a type that satisfies the trait bounds you can use todo!() as TheType. Not as convenient, but it works.

I mean... in theory we could just pick a random type automatically that satisfies the trait, but then someone would start relying on this and we have enough problems with things like the one-impl-rule which are a similar situation

@oli-obk
Copy link
Contributor

oli-obk commented Oct 8, 2024

Idea: we could make the error have a suggestion that suggests casting the todo!() to a random type that satisfies the trait (if one exists)

This way we don't create more language corner cases and allow you to fix all those todos with cargo check --fix

@hanna-kruppe
Copy link
Contributor

Currently such a cast gets flagged as unreachable (technically correct) so either that lint needs to get tweaked or the suggestion also needs to include a well-placed expect(unreachable_code).

@drdozer
Copy link

drdozer commented Oct 8, 2024

If you know a type that satisfies the trait bounds you can use todo!() as TheType.

Yeah, I get this, but during the initial sketching, you don't have any implementations. In an API-first design, there's nothing but the trait until the API has been sketched. So needing to stub the whole thing out with noop impls to stub any of it out is a massive pain.

Perhaps this is a "wrong problem" problem, and maybe instead there's a concept of "an unimplemented type" that's a bit like unimplemented code? Like a middle ground between "the type exists and is FOO" and "there is no type FOO", that's "There is no type FOO yet, but if there was, ...". And at some point in the compilation chain, the compilation would bail with "unimplemented types", but at least you'd be able to typecheck through them? This is probably a horrible idea for various reasons that I've not thought through.

Sorry not to be more help.

@dan-da
Copy link

dan-da commented Oct 30, 2024

I just hit this issue also. In my case my trait has a blocking run() method and a run_async() method. I wanted to provide an unimplemented default for each, so that trait implementor's ony need to impl appropriate run method, blocking or async.

unimplemented!() works just fine in run(), but won't compile in run_async(). This forces the trait implementor to define a run_async() even if it won't be used. That works, but is ugly.

I illustrate the above only to describe a real use-case where this limitation is problematic.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-diagnostics Area: Messages for errors, warnings, and lints A-impl-trait Area: `impl Trait`. Universally / existentially quantified anonymous types with static dispatch. C-enhancement Category: An issue proposing an enhancement or a PR with one. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.
Projects
None yet
Development

No branches or pull requests