Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why are trait bounds for Send on trait implementations ignored?

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 :-)?

like image 763
user1752169 Avatar asked Aug 10 '19 12:08

user1752169


1 Answers

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 }
like image 85
SOFe Avatar answered Nov 10 '22 21:11

SOFe