`todo!()` should prevent "unused_variables" warning

Probably you were thinking about *mut !. That type should be inhabited, because could operate on it in generic code:

use std::mem::MaybeUninit;

fn set_value<T>(f: impl FnOnce() -> T) -> T {
    unsafe {
        let mut x = MaybeUninit::<T>::uninit();
        // When `T == !`, this is `*mut !`.
        let p: *mut T = x.as_mut_ptr();
        p.write(f());
        x.assume_init()
    }
}

fn main() {
    set_value(|| loop {});
}

Offtopic: is there an existing discussion on the soundness of MaybeUninit<ZST>::uninit().assume_init(), or transmute::<ZST1, ZST2>()? This looks sound for "normal" ZSTs, i.e. unit types, non-capturing closures, function types, and compositions of those. But I have just realized that it is assumed that size_of::<Uninhabited>() == 0, while the above expressions where one of ZSTs is uninhabited are unsound.

Even for normal ZSTs it isn't sound to create one using transmute or assume_init if the existence of the ZST is used as unforgable token. For example to indicate that some initialization has happened or that interrupts are disabled.

That's library UB, not language UB. Depending on the way one uses the forged value, it may not be unsound.

My use case is deserializing adapter types (like adapters of iterators or futures) which accept non-capturing closures. There is no way to put a "function or non-capturing closure" bound on the type, and there is still no way to create functions and non-capturing closures, given their type, even though it's known that the type has a unique value. I have a newtype adapter for such clsoures & functions which implements Deserialize and Default via transmute::<(), F>(), and basically the only thing one can do with the resulting value is call the wrapped value, provided that F: FnMut. The FnMut bound is satisfied only for real closures and functions, not user-defined tokens (yes, this is also not strictly guaranteed, but it's true and looks unlikely to change in foreseeable future).

! type throws a wrench, because it implements FnMut, even though it's not really a function (actually, it doesn't, but I don't know whether it's a deliberate decision or an accident; the "! implements everything it can" logic would suggest the latter).

Side note, what is that error/help message supposed to mean. Reproduced:

fn f<F: FnMut()>() {}
fn main() {
    f::<u32>();
}
----
error[E0277]: expected a `FnMut()` closure, found `u32`
...
  = note: wrap the `u32` in a closure with no arguments: `|| { /* code */ }`
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^???

A suggestion that's maybe applicable when the closure is an argument. Should be suppressed when there is no argument and/or when an explicit type that doesn't meet the bound has been specified (e.g. by turbofish).

Seems like a sort of iffy suggestion in general if the type doesn't match the return type of the closure, IMO.

1 Like

I'm late to the party since I only found out about this thread through a related GH issue and since I don't use IRLO.

Regarding ! impl'ing all traits in existence / making todo!() (with inference fallback ! instead of ()) work as the return expression of -> impl Trait functions, we've had extensive technical discussions about this in Return-position impl Trait is not satisfied by divergent/never type · Issue #113875 · rust-lang/rust · GitHub which I'm assigned to and which @compiler-errors and I are inclined to close as WONTFIX as soon as we document it as a language / type system guarantee in the Reference (with a lang or types FCP).

I've only skimmed this thread but you've also started to dig into the technical details (traits with associated types, etc.).

I've stalled on opening a PR to the Reference but let me do that right now.

This would require implementing the trait for this brand new type, which causes problems in exactly the same cases where we cannot implement the trait for !, since it is possible to witness the real type of an existential.

I don't think it is possible to coerce ! to impl Trait if Trait has associated functions. Consider the following:

trait Trait { fn f() -> !; }
fn g<T: Trait>(_: fn() -> T) -> ! { T::f() }
fn h() -> impl Trait { todo!() }

fn what() -> ! { g(h) }

By its type, what must diverge, but where? h is never called, and no other part of the code panics or loops.

That being said, if Trait doesn't have associated types (or all associated types have been explicitly specified) and all functions of Trait accept an argument of type Self, &Self or &mut Self, the "brand new type" approach seems sensible.

I see that the last comment (over a month ago) in that thread is so one suggesting more helpful diagnostics. Implementing that would help alleviate some of the problems with this.

I don't think the bug you linked shouldn't be closed as won't fix until the diagnostics have been improved.

1 Like

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.