When can we have async fn in trait?

I have created a couple future in the weekend. Simply because we can't have async fn in trait. I got to be honest, it's a pain in ASS. all the boilerplate code, ugly pin, create a struct for every function returns a future, all those stuff, I really don't need to have in my code. I understand it's not a priority now.but I really hope it can be done ASAP. :slightly_frowning_face:

it seems async_trait uses a

Pin<Box<dyn core::future::Future<Output = ()> + Send + 'async>>

to acheive the async fn.

But I really don't like dyn thing, It feels slow down the app a little bit. I knew we can do better. Since if I handcode the future, it does not need to be a boxed dyn.

Async in traits has been in works for a long time. It is not a simple feature as it may seem like, and a bunch of big language features (Generic Associated Types and Impl Trait in Type Alias, to name the biggest 2) were implemented specifically as bulding blocks for async in traits. Currently Generic Associated Types are stable and Impl Trait in Type Alias is still work in progress, so it may be a while until they hit stable. For now there's a nightly feature if you want to test a basic implementation of async fns in traits.

14 Likes

I strongly recommend you thoroughly measure this before making any claims: there is a lot of “feeling” induced by Rust’s opt-in approach to heap allocation and dynamic dispatch. Remember that all managed runtimes only offer boxing and dynamic dispatch, and obviously Java, C#, and node.js nevertheless are successful in running high-performance applications.

7 Likes

I don't disagree, just remember that all of those runtimes have much faster allocation because they have moving GC.

Isn't that just even more reason to measure?

I do not want to measure as we all know it is hard to do. You have to simulate certain high pressure and the results are only valid for certain scenario.

It is not a life and death situation, I am just saying I don’t want to have an allocation if I don’t absolutely have to.

And we all know no allocation is faster. But that performance gain is noticeable or even making any sense, that is a different question. Just like we want to remove an unnecessary if 1=1 check, we know it won”t slowdown the app, but we still want to remove it.

1 Like

I keep hearing people say this, but stated without context it is not true! Putting something on the heap means it can stay there until dropped, while putting something in stack-allocated values means that it may be moved around many times, which does take up CPU resources.

Take as evidence that on all systems — old and modern — there is a huge difference in available memory size between stack and heap. This should tell you that system designers know very well that most data are better stored on the heap.

There is another issue that hasn’t yet been mentioned: Future instances without boxing can get really, really large, which can lead to stack overflows (if you keep them there) or it will make the inevitable heap allocation much less efficient when you hand the Future over to a thread-pool executor.

This indicates two things: you are not actually forced to figure out that last 0.5% of performance, otherwise you’d already have the numbers, and since you don’t really care enough about performance my recommendation would always be to go with the code that is easier to read, with less hidden details. #[async_trait] is useful at times, but it does add unseen complexity that jumps at you when you can least afford it — and async fn in traits would have very similar issues. This is — as far as I understand — the reason behind Rust’s explicit opt-in approach to everything, and it is why I’m using the language.


As a general note: the phrase “as we all know” can mean many things (most of them with negative connotations), but it does not imply a good argument.

5 Likes

OK, "as we all know": What I mean is: allocation means you need to allocate. The action allocate itself takes time, be how small it is. Which means it's always slower. Practically, most projects you can not tell the performance difference. People insist nodejs is faster, yet a lot rusters create webapp using rust. Sometimes, we just want to keep the code pure, no allocation unless necessary is a goal, especially if the compiler can resolve the issue. if not, async_trait is wonderful. Like you said, in c#, allocations happens all the time. If you compare the performance between rust and java, and c#, rust does NOT always win.too many factors kicks in on the performance. yet a lot rusters still prefer rust, it's just a matter of belief, I guess. My main language is c# in the past, I pretty much knew it inside/out. I use c# create TCP/IP applications, it works fine, no performance issue. But once I got to know rust, I immediately turned all my tcp/ip apps into rust. It's certainly not because my c# app runs slow. I just felt it makes sense. I don't have an app that urgently needs to finish and needs a lot async_trait feature. Or else, it's a no brainer to try async_trait. Heck, may even go back to c#/nodejs, whatever it is that I can get it done. Business is the priority, right?

Yes, I agree with almost all points! (Although I should point out that anyone who says “language X is faster than language Y” will probably not be hired by me because such a statement is incorrect independent of X and Y.)


Since this is the Rust internals forum and there is some pertinent technical detail in play, I’d like to point out one error, though:

This is not true. A heap allocation can be faster than growing the stack if the stack now needs previously unmapped pages of fresh memory, which means a page fault that may take a long time (even seconds!) to resolve in the O/S kernel. A stack allocation is still an allocation, some memory needs to be reserved for the data structure in either case. And two small heap allocations may very well be faster than one large one, especially when new memory needs to be mapped into the process to find a large enough contiguous range.


I’m pushing back on “Box is evil!” because I have seen it cause real harm to real codebases (and to smart programmers’ productivity). Rust offers a full palette of tools, a wise programmer will use them without prejudice.

5 Likes

Box is not evil, I used it all the time when it's approper. Actually I believe it's very fast. Since in c#, we boxin/boxout an integer all the time. I just hope we can have an official support for async trait soon. I hate to use async_trait crate now, and later on, I want to clean up my code to revert it to the standard way.