Exploring Dynamic Dispatch in Rust (2017)
26 comments
·March 23, 2025kd5bjo
cyco130
I've long entertained the idea of a language feature that allows returning unknown sized objects by value. The calling convention would generate two function calls, one to go get the size which would then be passed to alloca to reserve space for the return value and a second call to get the actual result. Not sure of all the implications but I think it might be an idea worth pursuing to make things like variable length arrays and structs with flexible members near-first class constructs in the language. Pseudocode:
function zero_filled_int_array(length: usize): int[length] {
...
}
The int[length] bit determines the return value of the size call and the main body constitutes the part that actually fills in the value.steveklabnik
In my understanding, Ada allows you to return unsized values, but it's generally implemented via a secondary stack.
cyco130
Gold mine! Didn't know about to concept, thanks.
Ericson2314
Looking forward to when Rust gets
1. erased types (have to use behind pointer, so not shallow dynamic sizing) 2. Existential types
So we can build vtables by hand.
Type safe API to any OOP ABI + tons of stuff OOP people never imagined....let's go!
littlestymaar
> 1. erased types (have to use behind pointer, so not shallow dynamic sizing)
How does &dyn MyTrait, which already exist today, not fit the description?
> 2. Existential types
What is that?
Ericson2314
I need to write the up because no one knows what either of these things are. Every single answer here is wrong.
ameliaquining
Yeah, this sounds pretty complicated to me but if there's any sketch of a design for how it could work then I'd be interested to read it.
tialaramex
Well I have no idea what your parent meant, but existential types (types which we assert to exist but don't specify) is in fact already used in Rust
fn goose_iterator() -> impl Iterator<Item=Goose>
That type on the right hand side, impl Iterator<Item=Goose> isn't the name of an actual type, that's just a claim about a property of this type, whatever it is. That's an existential type, the type exists, but we're not naming it.
In Rust this means goose_iterator() has some specific result type, but we're not allowed to know what that type is exactly (the compiler knows but our program does not), the only thing we're promised is that whatever type it is, does implement this trait with these parameters.
You'll see this often with things that return a callable - it's literally not possible to name most of Rust's callables, even though they each have a distinct type you can't utter the name of that type. So we need to use existential types for that case, but you can use them (as I did) to promise only that you're giving back an iterator without nailing down specifically which iterator that is - which would let you swap that iterator in an API without causing an API incompatibility.
conaclos
> Well I have no idea what your parent meant, but existential types (types which we assert to exist but don't specify) is in fact already used in Rust
Yes, but it is still quite limited: they are not yet supported as associated type in traits or in type aliases.
BlackFly
Existential types already exist as `impl Trait` for method parameters, unfortunately return position impl traits aren't fully existential because you can only return one type from a function.
Ericson2314
Existential quantification is type of variable bindings. You can't point to a think that doesn't bind a variable and say it's the same thing.
BlackFly
I have absolutely no idea what you're trying to say. The RPIT will be bound by the caller and there exists a type such that the declared trait is implemented. Meanwhile an impl parameter will be bound within the method with some type existing such that the trait is implemented. That is the implication of existential, "there exists a type such that...".
LoganDark
> 1. erased types (have to use behind pointer, so not shallow dynamic sizing)
What prevents one from using a pointer like this already? I'm curious.
> 2. Existential types
Fair, I think.
> So we can build vtables by hand. > > Type safe API to any OOP ABI + tons of stuff OOP people never imagined....let's go!
Is this not already possible?
steveklabnik
> Is this not already possible?
It is, the anyhow crate does this, for example. It does require unsafe though.
Ericson2314
I mean safely build vtables. Of course, we can already do everything unsafely.
pjmlp
You can already get there with std::any, also I imagine you will have some fun digging into COM and WinRT support work done by Microsoft.
Ericson2314
I want to safely build vtables by hand. Of course we can already do everything unsafely with nasty casts.
shmerl
If I recall correctly, Rust decisively wanted to avoid multiple inheritance concept, in order not to deal with the diamond problem.
Composition over inheritance is the suggested principle.
ameliaquining
This is not really related to the problems that the article is talking about. Rust doesn't have implementation inheritance at all, single or multiple, and "diamond impls" are prevented by the orphan rule.
A type implementing multiple traits is fine and normal, as is a type bound that requires type parameters to implement multiple traits; none of this poses any "diamond inheritance" type issues or other conceptual problems. It's only if you want to do dynamic dispatch in this situation that there are problems, because dynamic dispatch requires vtables and it's not clear how vtables for multiple traits would work; all known design proposals have serious drawbacks.
tyilo
Trait upcasting will land in Rust 1.86.0 which will be released in one week: https://github.com/rust-lang/rust/pull/134367
tyilo
(2017)
The examples don't compile without using Rust edition 2018 or older.
manmtstream
The memory layouts don’t work like this anymore
null
It's worth noting that this post is 8 years old, and some things have moved on since then.
In particular, trait object upcasting is on beta now and will be promoted to stable in a couple of weeks. I know that there has also been active development towards enabling multiple-bound trait objects, but I don't know what the status of that effort is.
The article also makes the (quite understandable) mistake of conflating a fat pointer to a trait object with the trait object itself. In Rust, trait objects are "unsized", which means they can only exist behind some kind of pointer. And any pointer in Rust that targets an unsized type grows an additional metadata field (for trait objects, a vtable pointer). This is a slightly weird behavior with a few unexpected consequences that often catches people off-guard, but understanding it is key to understanding why you can't make a trait object for something like `Clone`, which returns an instance of itself-- Because unsized types can't appear on their own, the return type of the `clone()` method would be invalid.
For a more thorough take, I recommend "A tour of `dyn Trait`" ( https://quinedot.github.io/rust-learning/dyn-trait-overview.... )