This is an irlo thread mainly because I don’t know exactly where it would go on the Rust tracker.
Even a minimal impl Debug for async fn-created futures would be very valuable for users of async fn, if only because more types would be able to be dbg!-traced.
A minimal implementation:
// line 1:
async fn foo() {
// line 2:
bar().await;
// line 3:
}
// expands to roughly
fn foo() -> impl Fututre<Output=()> {
enum __foo {
_2,
_3,
_4,
}
impl Future for __foo {
fn poll(..) -> _ {
match self {
_2 => { /* run from line 2 to 3 */ }
_3 => { /* run from line 3 to exit */ }
_4 => panic!(..),
}
}
}
impl Debug for __foo {
fn fmt(..) -> _ {
"async fn() {foo}"
}
}
__foo::_2
}
This could be confused for a debug implementation for the impl Fn ZST though (which I think we should provide as well! though it currently prints as fn() -> impl std::future::Future {foo} in error messages), so as an alternative that provides more information about the future state:
The idea being to provide the fn which the future came from as well as where it’s currently suspended.
(Rough specification: impl Fn ZST debug representation would be effectively the current compiler printing of the type (from an external crate), representing that it is a function type for the function at this path, and the async fn future debug implementation would be the async fn’s debug implementation followed by a representation of where the future is currently suspended.
Agreed. I think at some point in the future we should generate debug impls for all futures created from async items (using some sort of default {...} text for fields which don’t implement Debug).
This doesn’t interact with the Send issue if we do it this way because all async items will implement Debug, theres never a possibility of failing that.
Yes, but not in a way where we would need to have ?Debug or disallow having part of the state not implement Debug (we would just have the compiler-generated Debug impl properly handle those fields in some way, such as by eliding them with ...).
Leaving out Debug impls (and others) can be a rather large compile-time optimization, sometimes done intentionally. I know this is just talking about "some point in the future" but I'd like to avoid that cost somehow (even if that's "just" by optimizing the compiler itself so leaving out Debug impls is no longer such an improvement).
sure, all of these factors can be considered in the design. The important thing is that there are no backward compatibility hazards requiring us to work through this now.
AFAICT the only assumption that has to be made is that the Future is properly aligned, which will be the case - note that creating references to a packedFuture is ok.
What might not be properly aligned are the internal fields of the Future, which is probably ok as long as one does not create references to them. However, deriving Debug requires being able to create references to the fields of the Future, which can’t be done if the Future is packed.
More than correctness, I was wondering whether deriving Debug would impose constraints on how the compiler must lay out these types that do not exist today.
AFAIK the layout of these types is unspecified, would it be possible for the compiler to pack these types today, i.e., to put some fields at unaligned offsets within the Future to reduce padding ?
Has this been brought up anywhere before? I spent a while looking through rust-lang/{rust,rfcs} issues and couldn't find anything, it seems surprising that nobody would have mentioned it before since it seems like a super-useful feature. Even just printing the type_name or the representation used in compiler errors would be useful (so that when you see types like std::iter::Map you know which closure they're running on the items).
It doesn't seem possible to "derive" Debug for the generated futures, the layout used is not a simple struct or enum, it's a custom layout. So the async transform would have to generate a custom implementation of Debug for it, which could do whatever it needs to make it work with the layout. (Similar to how it currently generates a custom Drop implementation).