How to find out why Send was not derived automatically?

Hello everyone,

So the story kind of goes like this. You find a new library which implements some struct, that is composed of structs, that are composed of more structs (and so it goes up to some depth till "leaf" struct is composed of "primitve" data types). You try out the library with a simple example, and it works good enough, but when you decide to use it in an async function and await on some future you get an error, something like:

199 |    let result = ..
    |        ------ has type `SomeStruct` which is not `Send`

So my main question is - how do i find in this whole "struct tree" why Send was not derived automatically (like, search for offending struct that broke the Send automatic derive)?

I haven't seen any tool that would allow me to run a query on a code like "explain why SomeStruct is not Send", and it crawls through struct tree looking at each value and showing the deepest found offending child struct that is not Send. Even better would be if a tool could prove that it is not possible for struct to be Send and show a proof of it.

What is a neat way to deal with this? Wrap SomeStruct with something that allows Send (is it even possible to do it safely by not studying SomeStruct internals)? Manually searching for this takes more effort that i would like it to, especially when "struct tree" is wide and deep. But finding an answer to this question is somewhat important, because it limits the choices i can make to move forward. For example, check if struct is Send, fix the code and send a pull request to the author, maybe fork a library and fix locally or maybe redesign application so that SomeStruct is used only within a single thread and use a task queue, maybe find another library that does things "better" or "worse, but implements Send", etc.

Thanks in advance!

Edit: I missed the bit about this involving async when I wrote this (got distracted between reading your question and writing the test code). I don't know if this will work for the types implicitly generated by async code, but it should still be relevant for types that might be implicated.

So, I might be answering the wrong question, but maybe it's still useful?

Well, I threw an example at the playpen:

#![allow(dead_code)]

struct S {
    x: S2,
}

struct S2 {
    x: *const i32,
}

fn f() {
    fn g<T: Send>() {}
    g::<S>();
}

Which, when compiled, gave me this:

   Compiling playground v0.0.1 (/playground)
error[E0277]: `*const i32` cannot be sent between threads safely
  --> src/lib.rs:13:9
   |
13 |     g::<S>();
   |         ^ `*const i32` cannot be sent between threads safely
   |
   = help: within `S`, the trait `Send` is not implemented for `*const i32`
note: required because it appears within the type `S2`
  --> src/lib.rs:7:8
   |
7  | struct S2 {
   |        ^^
note: required because it appears within the type `S`
  --> src/lib.rs:3:8
   |
3  | struct S {
   |        ^
note: required by a bound in `g`
  --> src/lib.rs:12:13
   |
12 |     fn g<T: Send>() {}
   |             ^^^^ required by this bound in `g`

For more information about this error, try `rustc --explain E0277`.
error: could not compile `playground` (lib) due to previous error

Bit of an obvious question, but: have you made sure to check the entire error message from the compiler? Are you using an old compiler for which this extra output isn't generated?

2 Likes

I use rustc version 1.66.0, which when compiling your provided example outputs the same nice error message that pinpoints exact location for not Send.

For my current problem where compiler does not show so nice error is i use axum (web application framework that focuses on ergonomics and modularity) together with tokio and i wanted to use vrl to transform incoming textual data.

The problem in all this is due to axum's design. While it is very nice and flexible from use-case perspective, it is not very friendly with error messages, even when using their provided #[debug_handler] macro. So the best of what i get is an error message that specifies which struct in async request handler does not implement Send.

If i do not look too deep into VRL libraries details and impl Send manually, compiler is happy and everything compiles. But the thing is that to be sure that it is safe, i must look into internals, so a tool that helps finding it would be nice.

Your example actually gave me one idea i shall try out. If it works, i will document it here.

It turned out that what you wrote was useful.

So i just took the snippet and placed it somewhere at the beginning of a file. Got a nice error message which traverses down the tree and finds the struct that does not impl Send. It's exactly what i wanted, thus it's kind of a bit ironic, but rustc was the tool that i was looking for. :slight_smile: Just needed to find the right way to use it.

fn f() {
    fn g<T: Send>() {}
    g::<SomeStruct>();
}

So easy, shame i did not think of this myself :smiley:

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.