rust-lang / rust

Empowering everyone to build reliable and efficient software.
https://www.rust-lang.org
Other
97.96k stars 12.69k forks source link

! can't be cast to Fn(_) -> _ #120153

Open Pr0methean opened 9 months ago

Pr0methean commented 9 months ago

I tried this code:

#[derive(Debug)]
pub struct SharedBufferRng<const WORDS_PER_SEED: usize, const SEEDS_CAPACITY: usize, SourceType: Rng,
FallbackFn: Send + Sync + Clone + Fn() -> SourceType> {
    receiver: Receiver<DefaultableAlignedArray<WORDS_PER_SEED, u64>>,
    calling_thread_fallback: Option<FallbackFn>
}
impl<
        const WORDS_PER_SEED: usize,
        const SEEDS_CAPACITY: usize,
        SourceType: Rng + Send + Debug + 'static,
        FallbackFn: Sync + Send + Clone + Fn() -> SourceType
    > SharedBufferRng<WORDS_PER_SEED, SEEDS_CAPACITY, SourceType, FallbackFn>
    where [(); WORDS_PER_SEED * size_of::<u64>()]:, [(); WORDS_PER_SEED * SEEDS_CAPACITY * size_of::<u64>()]:
{
    /// Creates an RNG that will have a new dedicated thread reading from [source] into a new buffer that's shared with
    /// all clones of this [SharedBufferRng].
    pub fn new(mut source: SourceType, fallback: Option<FallbackFn>) -> Self {/*...*/}
}
#[test]
fn test() {
     let seeder: SharedBufferRng<8, 4, _, _> =
         SharedBufferRng::new(BlockRng64::new(source.clone()), None::<!>);
}

I expected to see this happen: the code would compile. Option<!> would be a valid type with zero size, and would cast to Option<T> for all T, because the Some variant would be eliminated during monomorphization. In fact None would always be of type Option<!> unless and until the compiler found a way for it to become Some.

Instead, this happened: it didn't compile until I changed the turbofish to ::<fn(_) -> _>

Meta

rustc --version --verbose:

rustc 1.77.0-nightly (fb5ed726f 2023-12-28)
binary: rustc
commit-hash: fb5ed726f72c6d16c788517c60ec00d4564b9348
commit-date: 2023-12-28
host: x86_64-apple-darwin
release: 1.77.0-nightly
LLVM version: 17.0.6
Backtrace

``` ```

clubby789 commented 9 months ago

Can you provide a minimal reproducible example for this issue?

Pr0methean commented 9 months ago

Here's one:

#![feature(never_type)]

fn maybe_run<T: Fn()>(optional_func: Option<T>) {
    match optional_func {
        Some(func) => func(),
        None => {}
    }
}

fn main() {
    maybe_run::<!>(None)
}
fmease commented 9 months ago

Here's one

That one just demonstrates that ! doesn't impl Fn

fmease commented 9 months ago

! is not the bottom type – the subtype of all types. ! can only be coerced to T for any type T. This coercion isn't transitive: You can't coerce (!,) to (T,) for any type T for example. Same with Option<!> and Option<T> for any type T.

fmease commented 9 months ago

If you want to coerce Option<!> to Option<T> for any type T, you'll need to do it manually (and “lift the coercion”), e.g. via map: o.map(|x| x).

Pr0methean commented 9 months ago

If ! isn't the bottom type (as I'd understood it to be), then how is the actual bottom type (one that e.g. when used as a field type in an enum variant, deletes that variant during monomorphization) represented?

Pr0methean commented 9 months ago

IMO the ! type won't be doing its job properly until rustc --release can make Vec::<!>::len() and size_of::<Vec<!>>() and size_of::<Option<!>>() all return zero and fold that zero as a compile-time constant.

Pr0methean commented 9 months ago

And this is certainly doable, since for example Kotlin has a Nothing type (used as the return type for methods that are provably non-halting or that always throw an exception) even when compiling for the JVM (which doesn't have a bottom type).

kadiwa4 commented 9 months ago

Fn() -> SourceType (from the first example you posted) is just a trait, with fn() -> SourceType only being one of the possible types it could be. So implicitly casting None::<!> to None::<fn() -> SourceType> – just based on the requirement that the type has to implement Fn() -> SourceType – wouldn't make any sense to me. Especially because an implementation of Fn(..) -> _ for ! could be added at some point which doesn't sound like a bad idea actually. Is that what you were trying to suggest with this issue?

But maybe what you meant is something like this:

#![feature(never_type)]

fn maybe_run(optional_func: Option<fn()>) {
    match optional_func {
        Some(func) => func(),
        None => {}
    }
}

fn main() {
    maybe_run(None::<!>)
}

In which case fmease's answer applies.

size_of::<Vec<!>>() won't be zero anytime soon because you can't currently have specialized structs for specific type parameters. size_of::<Option<!>>() is already zero as a compile-time constant because the Some variant is deleted as you say.

Pr0methean commented 9 months ago

I agree that impl Fn() -> _ for ! is what I meant to suggest, since the first example was designed to monomorphize for both method references and closures with and without captured variables.