How does method resolution actually work?

I was trying to write some test cases making sure certain types implement/don't implement certain traits. I figured I could use method resolution/autoderef for this, like so:

use core::marker::PhantomData;

pub struct NonImplSync {
    _private: ()
}

pub struct TestSync<T> {
    non_impl: NonImplSync,
    phantom: PhantomData<T>,
}

impl<T> TestSync<T> {
    pub fn from_value(_: T) -> Self {
        Self::from_type()
    }

    pub fn from_type() -> Self {
        TestSync { non_impl: NonImplSync { _private: () }, phantom: PhantomData }
    }
}

impl<T: Sync> TestSync<T> {
    pub fn is_sync(&self) -> bool {
        true
    }
}

impl NonImplSync {
    pub fn is_sync(&self) -> bool {
        false
    }
}

impl<T> core::ops::Deref for TestSync<T> {
    type Target = NonImplSync;
    
    fn deref(&self) -> &NonImplSync {
        &self.non_impl
    }
}

fn main() {
    let v = core::cell::Cell::new(0);
    let non_sync_closure = move || { v; };
    let sync_closure = || {};

    println!("{}", TestSync::<&(dyn Fn() + Sync)>::from_type().is_sync()); // prints true
    println!("{}", TestSync::<&dyn Fn()>::from_type().is_sync()); // prints false
    println!("{}", TestSync::from_value(sync_closure).is_sync()); // prints true
    println!("{}", TestSync::from_value(non_sync_closure).is_sync()); // compile error: the trait `Sync` is not implemented for `[closure]`
}

Now the issue is, this approach seems to work totally fine with the from_type constructor, but it breaks for from_value. From reading the reference it's not clear to me why there'd be a different behavior for values of type TestSync::<&dyn Fn()> and TestSync::<[concrete non-Sync closure]>.

I'd like to update the reference to clarify this, can anyone explain what it should say?

The specific behavior seems related to auto traits and closures, maybe this is just a bug?

I get the same thing when using something like this instead of Sync:

#![feature(auto_traits, negative_impls)]

auto trait MyTrait {}

impl<T: ?Sized> !MyTrait for core::cell::Cell<T> {}

But something like this works fine:

trait MyTrait {}

impl<F: Fn(i32)> MyTrait for F {}

Auto traits also work fine when using explicitly-defined structs or functions with impl Trait return values.

Just for completeness, note that this also works:

fn main() {
    let v = core::cell::Cell::new(0);
    let non_sync_closure = move || { v; };
    let non_sync_closure_dyn_ref: &dyn FnOnce() = &non_sync_closure;
    println!("{}", TestSync::from_value(non_sync_closure_dyn_ref).is_sync()); // prints false
}

so the difference between from_value and from_type is irrelevant. It’s about the closure types vs something concrete like &dyn FnOnce().

It seems almost as if the compiler / type checker still treats the non_sync_closure as a not 100% fully inferred type when doing the method resolution in your example (which doesn’t make much sense though. I even tried writing 0i32 instead of the 0 to make sure that is not the problem).

1 Like