8

Why are trait bounds for auto trait Send on trait implementations ignored? (Playground(1))

trait IsSend {
    fn is_send(&self);
}

impl<T: Send> IsSend for T {
    fn is_send(&self) {}
}

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let i = std::rc::Rc::new(43);
    i.is_send(); // (!!) no compiler error although Rc<...> is not Send
    Ok(())
}

For example using a trait bound for a self defined trait (X) it works: (Playground(2))

trait X {}

trait IsSend {
    fn is_send(&self);
}

impl<T: X> IsSend for T {
    fn is_send(&self) {}
}

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let i = std::rc::Rc::new(43);
    i.is_send(); // (ok) compiler error as Rc<...> does not implement X
    Ok(())
}

Even more confusing, using a trait bound on a function it works as expected: (Playground(3))

fn is_send<T: Send>(_s: &T) {}

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let i = std::rc::Rc::new(43);
    is_send(&i); // (ok) compiler as Rc<...> is not Send
    Ok(())
}

It looks like the auto trait Send (or auto traits in general) is treated specially. However, I have not found any documentation about this. Is this a bug or just my lack of understanding :-)?

SOFe
  • 7,134
  • 4
  • 28
  • 57
user1752169
  • 383
  • 2
  • 7

1 Answers1

2

Have a look at this slightly modified version of your playground

use std::any::TypeId;

trait IsSend {
    fn is_send(&self);    
}

impl<T: Send + 'static> IsSend for T {
    fn is_send(&self){
        println!("TypeId of T: {:?}", TypeId::of::<T>());
    }
}

fn main() -> Result<(),Box<dyn std::error::Error>> {
    println!("TypeId of i32: {:?}", TypeId::of::<i32>());
    println!("TypeId of Rc<i32>: {:?}", TypeId::of::<std::rc::Rc<i32>>());

     let i = std::rc::Rc::new(43);
     i.is_send(); // (!!) no compiler error although Rc is not Send
     Ok(())
}

And we have the result:

TypeId of i32: TypeId { t: 13431306602944299956 }
TypeId of Rc<i32>: TypeId { t: 1918842018359854094 }
TypeId of T: TypeId { t: 13431306602944299956 }

I changed:

  • Added a few println! calls so that we can see the TypeId of the types
  • Added a requirement T: 'static, due to TypeId constraints. This should not affect our answer, since both Rc<i32> and i32 are 'static.

It can be seen that T is resolved as i32 instead of Rc<i32>. That is, is_send is called with T = i32 rather than T = Rc<i32>.

This is because Rc<T> implements Deref<Target = T>. When you call i.is_send(), it is actually equivalent to (*i).is_send(), and *i is an i32, which is a Send. The compiler attempts to perform dereferencing when you use the dot operator to call a method on a value until the type bounds are satisfied.

To show this, let's try changing Rc to Arc, where Arc implements Send. You can see that T now has the same TypeId as Arc<i32> rather than i32. This is because Arc already satisfies the T: Send bound, and no further dereferencing is required.

use std::any::TypeId;
use std::sync::Arc;

trait IsSend {
    fn is_send(&self);    
}

impl<T: Send + 'static> IsSend for T {
    fn is_send(&self){
        println!("TypeId of T: {:?}", TypeId::of::<T>());
    }
}

fn main() -> Result<(),Box<dyn std::error::Error>> {
    println!("TypeId of i32: {:?}", TypeId::of::<i32>());
    println!("TypeId of Arc<i32>: {:?}", TypeId::of::<Arc<i32>>());

     let i = Arc::new(43);
     i.is_send(); // (!!) no compiler error although Rc is not Send
     Ok(())
}
TypeId of i32: TypeId { t: 13431306602944299956 }
TypeId of Arc<i32>: TypeId { t: 3504454246784010795 }
TypeId of T: TypeId { t: 3504454246784010795 }
SOFe
  • 7,134
  • 4
  • 28
  • 57