Would it make sense for never to implement Iterator?

In that case ! should coerce, making the workaround unnecessary.

1 Like

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<_>
}
4 Likes

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.

2 Likes

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".)


  1. at least for traits like Iterator where every method has a self argument that makes them uncallable for uninhabited Self types ↩︎

  2. 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 being dyn-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 consider dyn a necessary restriction, even if this is a bit conservative. ↩︎

1 Like

If the unreachable-after-todo! warnings get silenced:

pub fn foo() -> impl Iterator<Item = i32> {
    std::iter::once(todo!())
}
1 Like

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.

4 Likes