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

impl Trait fails to resolve when returning ! #36375

Open
Cobrand opened this issue Sep 10, 2016 · 11 comments
Open

impl Trait fails to resolve when returning ! #36375

Cobrand opened this issue Sep 10, 2016 · 11 comments
Labels
A-impl-trait Area: impl Trait. Universally / existentially quantified anonymous types with static dispatch. A-traits Area: Trait system A-typesystem Area: The type system C-bug Category: This is a bug. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.

Comments

@Cobrand
Copy link
Contributor

Cobrand commented Sep 10, 2016

I couldn't think of a better title.

This code doesn't compile on nightly :

#![feature(conservative_impl_trait)]
use std::ops::Add ;

fn test() -> impl Add<u32> {
    unimplemented!()
}

fn main() {}
rustc 1.13.0-nightly (378195665 2016-09-08)
error[E0277]: the trait bound `(): std::ops::Add<u32>` is not satisfied

while this does :

#![feature(conservative_impl_trait)]
use std::ops::Add ;

fn test() -> impl Add<u32> {
    if true {
        unimplemented!()
    } else {
        0
    }
}

fn main() {}
@apasel422 apasel422 added A-traits Area: Trait system A-typesystem Area: The type system labels Dec 28, 2016
@Mark-Simulacrum Mark-Simulacrum added the C-bug Category: This is a bug. label Jul 26, 2017
@cramertj
Copy link
Member

cramertj commented Jan 5, 2018

Currently, ! does not implement Add<u32>. That's the underlying issue here. In order to fix this in general, IMO we should generate automatic ! impls of all traits containing only non-static methods.

@varkor
Copy link
Member

varkor commented Jan 12, 2018

Considering that ! can coerce to any other type, it seems strange that it can't coerce to any impl Trait (even if it doesn't explicitly implement that trait). It would seem like an inconsistency to only implement traits that contain non-static methods. As long as an object exists that implements that trait, ! could trivially coerce to that type, so you could do it by casting beforehand. I think this should "just work" (assuming there aren't any compatibility issues with doing it).

@hanna-kruppe
Copy link
Contributor

The issue is which type you coerce to. There can be multiple, and the choice can be observed if there are static methods: https://play.rust-lang.org/?gist=f8679a05f75f3db559870ad2389713bb&version=nightly

@varkor
Copy link
Member

varkor commented Jan 12, 2018

@rkruppe: Ah, I see; that's more awkward. The "non-static implementations only" causes issues too, though, because you'd always be able to get around it with this unintuitive if true { ... } else { valid_value } construction. Tricky.

@hanna-kruppe
Copy link
Contributor

if true { <diverges> } else { <something of type T> } unambiguously has type T. So there's no ambiguity. It's just a very ugly workaround.

@varkor
Copy link
Member

varkor commented Jan 12, 2018

Yeah, it's like explicitly casting the ! to any other type (that implements the trait) in the return position — it just feels very ugly, like you say (though casting is possibly somewhat nicer — and in the worst case, we could probably lint this case).

@kennytm
Copy link
Member

kennytm commented Jan 12, 2018

cc #34511 (comment)

@bddap
Copy link

bddap commented Mar 28, 2019

If () implements the return trait, compilation succeeds.

use std::fmt::Debug;

fn main() {
    println!("{:?}", frob());
}

fn frob() -> impl Debug {
    unimplemented!()
}

@obi1kenobi
Copy link
Member

I recently got bitten by this while working with Iterator, which led me to this issue.

The impl Trait RFC names impl Iterator as a major use case for that syntax, since it's a place where exact types get very complex, but code frequently only cares about the impl Iterator part. With current limitations in place, one has to choose between two unappealing options:

  • don't use todo!() or unimplemented!(), which are awesome for prototyping, or
  • specify the exact type, and don't use impl Iterator, which the impl Trait RFC eloquently argues is very unergonomic.

I understand that returning ! in general for impl Trait is difficult to do — but given that impl Iterator is a primary use case of impl Trait, would it make sense to special-case support for returning ! in impl Iterator functions?

As one possible approach, ! could perhaps be made to implement Iterator and simply have a next() that always returns None. A code example is below, though it currently doesn't compile because neither ! nor Iterator are defined in the current crate:

#![feature(never_type)]

impl Iterator for ! {
    type Item = u32;

    fn next(&mut self) -> Option<u32> {
        None
    }
}

fn my_func() -> impl Iterator<Item = u32> {
    todo!()
}

https://play.rust-lang.org/?version=nightly&mode=debug&edition=2018&gist=c754a0e11b6bc3931d124e41681b2562

I have not figured out a way to implement Iterator<Item = T> for any T, since ! is not generic itself and I don't control its definition so I can't add a PhantomData<&T>. But I'm fairly new to Rust, so perhaps more experienced folks might have some ideas.

Thanks for all your hard work making Rust awesome!

@kupiakos
Copy link
Contributor

kupiakos commented Jan 28, 2022

How about this? When ! is coerced to impl Trait, the compiler creates an impl Trait for ! with the following rules:

  • All associated functions panic, along the lines of panic!("<! as FooTrait>::baz()")
  • All associated constants panic, replaced with a panic!("<! as FooTrait>::BAR"). It could either set the value of the constant as panic!(), possible now with panic in const fn, or there could be special glue that replaces all access in non-const contexts with a panic to still allow it to compile. I prefer the former, as it's more consistent with the rest of the language. Fortunately, you only run into the compile-time panic if you actually access the associated constant.
  • All associated types are !, following the same impl Trait rules in case of trait bounds
  • All necessary supertraits are implemented with the same rules

@kpreid
Copy link
Contributor

kpreid commented Mar 11, 2022

When ! is coerced to impl Trait, the compiler creates an impl Trait for !

As stated, that would violate trait coherence, since the crate defining Trait should be allowed to impl Trait for !. That could be worked around: for purposes of the -> impl Trait type resolution, the function could pretend to return an anonymous implementing type that isn't actually ! itself.

A more serious surprise would be action-at-a-distance panics: if the return type gets unified with some other type variable, then the impl's associated functions could be called, and panic, even if the !-returning function is not ever called at all. The following program is a toy example which panics on current nightly, and if the above were implemented, would panic without the impl Foo for ! — without any explicit panics or triggering any library function panics:

#![feature(never_type)]
trait Foo {
   fn recommended_quantity() -> usize;
}

fn do_thing<T: Foo>(initialize: bool, f: fn() -> T) -> Vec<T>  {
    let mut v = Vec::with_capacity(T::recommended_quantity());
    if initialize {
        v.resize_with(T::recommended_quantity(), f);
    }
    v
}

impl Foo for ! {
    fn recommended_quantity() -> usize {
        panic!("hypothetically, this implementation is implicit");
    }
}

fn main() {
    fn never() -> ! { loop {} }
    do_thing(false, never);
}

Notice in particular that the -> ! function isn't even panicking; it's looping infinitely, which could be an entirely sensible main-loop function, perhaps (though not one you'd likely want to pass to this do_thing() example). The proposed implicit impl converts it from a compilation failure fixable by adding an impl, to a program which panics before it even calls the diverging function.

One way to describe this problem would be that it violates the suggested principle of implementing for !:

When writing your own traits, ! should have an impl whenever there is an obvious impl which doesn’t panic!.

This would implicitly make impls which observably do panic.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-impl-trait Area: impl Trait. Universally / existentially quantified anonymous types with static dispatch. A-traits Area: Trait system A-typesystem Area: The type system C-bug Category: This is a bug. 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