In that case ! should coerce, making the workaround unnecessary.
Ah, you can also force coercion with as, e.g. this works without any warning:
fn foo() -> impl Iterator<Item = i32> {
todo!() as core::iter::Empty<_>
}
I see, so something like this is possible but it still requires using never_iter manually and potentially specifying the generic type explicitly.
struct NeverIter<T>(!, std::marker::PhantomData<T>);
impl<T> Iterator for NeverIter<T> {
type Item = T;
fn next(&mut self) -> Option<Self::Item> {
None
}
}
fn never_iter<T>(never: !) -> NeverIter<T> {
NeverIter(never, Default::default())
}
fn foo() -> impl Iterator<Item = i32> {
never_iter(todo!())
}
fn main()
{
for x in never_iter::<u32>(todo!()) {
}
}
! could also have a method into_iter::<T>(self) -> NeverIter<T>, but that would stil require todo!().into_iter() yeah.
i would personally write the body of next as self.0 to make the compiler's job easier, but it doesn't matter much tbh.
Regarding the following tangent:
I believe the restriction would have to go a bit further or be clarified at least: to refer to dyn-compatible traits[2] only (as it is already trivial to "name out of thin air" implementations/implementing-types with arbitrary assoc types: dyn Trait<Assoc = WhateverYouWant>).
OTOH, for non-dyn-compatible traits, this could be a safety footgun, as the very nameability of certain impl Trait types can play a safety role.
To illustrate, here is a somewhat contrived example:
use Sized as NonDynCompatible;
pub(crate) mod seal { pub trait Sealed {} }
/// As per `pub(crate)`-sealing, it is true that for every
/// implementation, `Self::Assoc : Send` holds.
pub trait MyAssocIsSend : seal::Sealed + NonDynCompatible {
type Assoc;
}
/* notably if there are *no* implementations 😅, which I'll be
doing to keep the example shorter */
pub struct Wrapper<R: MyAssocIsSend> {
pub value: R::Assoc,
}
unsafe // Safety: `R::Assoc : Send` as per the invariant on impls.
impl<R: MyAssocIsSend> Send for Wrapper<R> {}
We would be able to exploit it with:
fn foo<'r>() -> impl MyAssocIsSend<Assoc = &'r Cell<bool>> {
unimplemented!()
}
fn wrap<'r, R: MyAssocIsSend<Assoc = &'r Cell<bool>>(
value: &'r Cell<bool>,
// even if never called, it's giving us
// `R = impl MyAssocIsSend<Assoc = &'r Cell<bool>>`
_witness_fn_never_called: impl FnOnce() -> R,
)
{
let wrappers = (Wrapper { value }, Wrapper { value });
::std::thread::scope(|s| {
_ = s.spawn(move || { wrappers.0 }.value.set(true));
_ = s.spawn(move || { wrappers.1 }.value.set(false));
});
}
wrap(&Cell::new(false), foo);
(More generally, it's important to distinguish uninhabited at runtime from "uninhabited in the type system".)
at least for traits like
Iteratorwhere every method has aselfargument that makes them uncallable for uninhabitedSelftypes ↩︎more generally, I view
dyn-compatible trait as a subset of "traits as bags of methods". And this qualification does matter: let us remember that not all traits in Rust are "bags of methods"! Some convey type-level properties that go beyond their very methods (of which there may even be none). So beingdyn-compatible is sufficient to be in the right category. It ought not be necessary, but since the boundaries of what exactly count as "bags of methods"-traits are a bit fuzzy, I'd rather err on the side of caution and considerdyna necessary restriction, even if this is a bit conservative. ↩︎
If the unreachable-after-todo! warnings get silenced:
pub fn foo() -> impl Iterator<Item = i32> {
std::iter::once(todo!())
}
this works on nightly: Rust Playground
pub fn foo() -> impl Iterator<Item = i32> {
gen { todo!() }
}
so we "just" need to wait for stabilization
That has slightly different semantics though, it panics on first iteration instead of creation.