Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

A "zero-cost abstraction" really means that abstraction doesn't impose a cost over the optimal implementation of the task it is abstracting. Some things—like chaining a dynamic number of (arbitrary) closures—fundamentally require some sort of dynamic allocation/construction, and so a zero-cost abstraction would be one that it only does that dynamic behaviour when necessary.

If you don't need the dynamic behaviour, the library is a zero-cost abstraction, by statically encoding all the pieces at the type level: like Rust's iterators, each combinator function returns a new future type that contains all information about its construction and operations. To add to this, a closure in Rust is not a function pointer, each one a specialized struct containing exactly the captures, and thus this all gets put into the type information too, and everything can be be inlined together into a single pipeline.

However, if you are dynamically constructing the future you'll have to opt-in to a uniform representation for the parts (i.e. erase the type information about the different constructions). This does indeed require allocating and storing pointers, but AFAICT this is required in any implementation, i.e. this library imposes no/little extra overhead over the optimal hand-written implementation.

Furthermore, the static and dynamic parts can work together: if you have parts that are statically known, these can be constructed as a single static type (with no function pointers or allocations), and then boxed up into a dynamic future as a whole unit, which can then also form part of other static chains, meaning allocations and dynamic calls only need to happen when absolutely necessary.



Thanks for your reply. I'm still trying to understand what the restrictions look like.

Say I sometimes have an outstanding asynchronous operation, i.e. validating some text in a document. I want to represent this by storing a Future representing this operation:

    struct Document {
        text:String,
        validating: Option<Future<Bool>>
    };
It seems like there's a problem: in order to have a struct of this type, we need to have the type of the Future, but the only way to have the type of the Future is to create the Future (and here we have None).

It this struct possible? How would I initialize it with {"foo", None}?


The construct wouldn't be valid as written for the reasons you mention. The blog post for this discussion glosses over the details by writing `impl Future<Item = Bool>` instead of the concrete type a future would have to have in Rust today.

I'm not an expert but one of the ongoing dicussions in the impl Trait RFC is where the construct can show up. The preliminary version can only appear as a return type from a function where the compiler can always determine the exact details at compile time depending on the chain of calls. I believe typing out the entire type of the future in the struct would work but, again, I only somewhat know my way around the language.


One solution is to put the Future into a Box.


And I can't believe I missed this, but the other way is to make the struct generic over a type implementing Future.


Does this work though? How do you create an instance of the struct without an instance of the Future?


    trait Future {}
    
    struct DynamicFoo {
        text:String,
        validating: Option<Box<Future>>
    }
    
    struct StaticFoo<T: Future> {
        text:String,
        validating: Option<T>
    }
These are the two options. In the first, Box<Future> stores a "trait object", that is, a tuple of (data ptr, vtable ptr). In the second, there will be a struct for each type used to generate a StaticFoo.

    impl Future for i32 {}
    impl Future for f64 {}
    
    fn main() {
        let d = DynamicFoo {
            text: String::from("foo"),
            validating: Some(Box::new(5) as Box<Future>),
        };
        
        let s1 = StaticFoo {
            text: String::from("foo"),
            validating: Some(5),
        };
        
        let s2 = StaticFoo {
            text: String::from("foo"),
            validating: Some(5.0),
        };
    }
Works just fine.




Consider applying for YC's Winter 2026 batch! Applications are open till Nov 10

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: