impl Trait fails to resolve when returning !
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() {}
Currently,
!does not implementAdd<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.Taylor Cramer at 2018-01-05 21:10:07
Considering that
!can coerce to any other type, it seems strange that it can't coerce to anyimpl 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).varkor at 2018-01-12 11:22:04
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
Hanna Kruppe at 2018-01-12 11:35:27
@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.varkor at 2018-01-12 11:41:20
if true { <diverges> } else { <something of type T> }unambiguously has typeT. So there's no ambiguity. It's just a very ugly workaround.Hanna Kruppe at 2018-01-12 12:58:28
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).varkor at 2018-01-12 13:24:23
cc https://github.com/rust-lang/rust/issues/34511#issuecomment-322340401
kennytm at 2018-01-12 14:31:21
If
()implements the return trait, compilation succeeds.use std::fmt::Debug; fn main() { println!("{:?}", frob()); } fn frob() -> impl Debug { unimplemented!() }Andrew Dirksen at 2019-03-28 18:08:27
I recently got bitten by this while working with
Iterator, which led me to this issue.The
impl TraitRFC namesimpl Iteratoras a major use case for that syntax, since it's a place where exact types get very complex, but code frequently only cares about theimpl Iteratorpart. With current limitations in place, one has to choose between two unappealing options:- don't use
todo!()orunimplemented!(), which are awesome for prototyping, or - specify the exact type, and don't use
impl Iterator, which theimpl TraitRFC eloquently argues is very unergonomic.
I understand that returning
!in general forimpl Traitis difficult to do — but given thatimpl Iteratoris a primary use case ofimpl Trait, would it make sense to special-case support for returning!inimpl Iteratorfunctions?As one possible approach,
!could perhaps be made to implementIteratorand simply have anext()that always returnsNone. A code example is below, though it currently doesn't compile because neither!norIteratorare 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 anyT, since!is not generic itself and I don't control its definition so I can't add aPhantomData<&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!
Predrag Gruevski at 2021-03-02 23:54:11
- don't use
How about this? When
!is coerced toimpl Trait, the compiler creates animpl 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 aspanic!(), 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 sameimpl Traitrules in case of trait bounds - All necessary supertraits are implemented with the same rules
Alyssa Haroldsen at 2022-01-28 19:41:54
- All associated functions panic, along the lines of
When
!is coerced toimpl Trait, the compiler creates animpl Trait for !…As stated, that would violate trait coherence, since the crate defining
Traitshould be allowed toimpl Trait for !. That could be worked around: for purposes of the-> impl Traittype 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 theimpl 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 thisdo_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’tpanic!.This would implicitly make impls which observably do panic.
Kevin Reid at 2022-03-11 05:39:27