| 1 | Proposal For Use of Virtual Tables
 | 
|---|
| 2 | ==================================
 | 
|---|
| 3 | 
 | 
|---|
| 4 | This is an adaptation of the earlier virtual proposal, updating it with new
 | 
|---|
| 5 | ideas, re-framing it and laying out more design decisions. It should
 | 
|---|
| 6 | eventually replace the earlier proposal, but not all features and syntax have
 | 
|---|
| 7 | been converted to the new design.
 | 
|---|
| 8 | 
 | 
|---|
| 9 | The basic concept of a virtual table (vtable) is the same here as in most
 | 
|---|
| 10 | other languages. They will mostly contain function pointers although they
 | 
|---|
| 11 | should be able to store anything that goes into a trait.
 | 
|---|
| 12 | 
 | 
|---|
| 13 | I also include notes on a sample implementation, which primarly exists to show
 | 
|---|
| 14 | there is a resonable implementation. The code samples for that are in a slight
 | 
|---|
| 15 | psudo-code to help avoid name mangling and keeps some CFA features while they
 | 
|---|
| 16 | would actually be writen in C.
 | 
|---|
| 17 | 
 | 
|---|
| 18 | Trait Instances
 | 
|---|
| 19 | ---------------
 | 
|---|
| 20 | 
 | 
|---|
| 21 | Currently traits are completely abstract. Data types might implement a trait
 | 
|---|
| 22 | but traits are not themselves data types. This will change that and allow
 | 
|---|
| 23 | instances of traits to be created from instances of data types that implement
 | 
|---|
| 24 | the trait.
 | 
|---|
| 25 | 
 | 
|---|
| 26 |     trait combiner(otype T) {
 | 
|---|
| 27 |                 void combine(T&, int);
 | 
|---|
| 28 |         };
 | 
|---|
| 29 | 
 | 
|---|
| 30 |     struct summation {
 | 
|---|
| 31 |                 int sum;
 | 
|---|
| 32 |         };
 | 
|---|
| 33 | 
 | 
|---|
| 34 |         void ?{}( struct summation & this ) {
 | 
|---|
| 35 |                 this.sum = 0;
 | 
|---|
| 36 |         }
 | 
|---|
| 37 | 
 | 
|---|
| 38 |     void combine( struct summation & this, int num ) {
 | 
|---|
| 39 |                 this.sum = this.sum + num;
 | 
|---|
| 40 |         }
 | 
|---|
| 41 | 
 | 
|---|
| 42 |         trait combiner obj = struct summation{};
 | 
|---|
| 43 |         combine(obj, 5);
 | 
|---|
| 44 | 
 | 
|---|
| 45 | As with `struct` (and `union` and `enum`), `trait` might be optional when
 | 
|---|
| 46 | using the trait as a type name. A trait may be used in assertion list as
 | 
|---|
| 47 | before.
 | 
|---|
| 48 | 
 | 
|---|
| 49 | For traits to be used this way they should meet two requirements. First they
 | 
|---|
| 50 | should only have a single polymorphic type and each assertion should use that
 | 
|---|
| 51 | type once as a parameter. Extentions may later loosen these requirements.
 | 
|---|
| 52 | 
 | 
|---|
| 53 | If a trait object is used it should generate a series of implicate functions
 | 
|---|
| 54 | each of which implements one of the functions required by the trait. So for
 | 
|---|
| 55 | combiner there is an implicate:
 | 
|---|
| 56 | 
 | 
|---|
| 57 |     void combine(trait combiner & this, int);
 | 
|---|
| 58 | 
 | 
|---|
| 59 | This function is the one actually called at the end
 | 
|---|
| 60 | 
 | 
|---|
| 61 | The main use case for trait objects is that they can be stored. They can be
 | 
|---|
| 62 | passed into functions, but using the trait directly is prefred in this case.
 | 
|---|
| 63 | 
 | 
|---|
| 64 |     trait drawable(otype T) {
 | 
|---|
| 65 |         void draw(Surface & to, T & draw);
 | 
|---|
| 66 |         Rect(int) drawArea(T & draw);
 | 
|---|
| 67 |     };
 | 
|---|
| 68 | 
 | 
|---|
| 69 |     struct UpdatingSurface {
 | 
|---|
| 70 |         Surface * surface;
 | 
|---|
| 71 |         vector(trait drawable) drawables;
 | 
|---|
| 72 |     };
 | 
|---|
| 73 | 
 | 
|---|
| 74 |     void updateSurface(UpdatingSurface & us) {
 | 
|---|
| 75 |         for (size_t i = 0 ; i < us.drawables.size ; ++i) {
 | 
|---|
| 76 |             draw(us.surface, us.drawables[i]);
 | 
|---|
| 77 |         }
 | 
|---|
| 78 |     }
 | 
|---|
| 79 | 
 | 
|---|
| 80 | Currently these traits are limited to 1 trait parameter and functions should
 | 
|---|
| 81 | have exactly 1 parameter. We cannot abstract away pairs of types and still
 | 
|---|
| 82 | pass them into normal functions, which take them seperately.
 | 
|---|
| 83 | 
 | 
|---|
| 84 | The second is required the because we need to get the vtable from somewhere.
 | 
|---|
| 85 | If there are 0 trait objects than no vtable is avalible, if we have more than
 | 
|---|
| 86 | 1 than the vtables give conflicting answers on what underlying function to
 | 
|---|
| 87 | call. And even then the underlying type assumes a concrete type.
 | 
|---|
| 88 | 
 | 
|---|
| 89 | This loop can sort of be broken by using the trait object directly in the
 | 
|---|
| 90 | signature. This has well defined meaning, but might not be useful.
 | 
|---|
| 91 | 
 | 
|---|
| 92 |     trait example(otype T) {
 | 
|---|
| 93 |         bool test(T & this, trait example & that);
 | 
|---|
| 94 |     }
 | 
|---|
| 95 | 
 | 
|---|
| 96 | #### Sample Implementation
 | 
|---|
| 97 | A simple way to implement trait objects is by a pair of pointers. One to the
 | 
|---|
| 98 | underlying object and one to the vtable.
 | 
|---|
| 99 | 
 | 
|---|
| 100 |     struct vtable_drawable {
 | 
|---|
| 101 |         void (*draw)(Surface &, void *);
 | 
|---|
| 102 |         Rect(int) (*drawArea)(void *);
 | 
|---|
| 103 |     };
 | 
|---|
| 104 | 
 | 
|---|
| 105 |     struct drawable {
 | 
|---|
| 106 |         void * object;
 | 
|---|
| 107 |         vtable_drawable * vtable;
 | 
|---|
| 108 |     };
 | 
|---|
| 109 | 
 | 
|---|
| 110 | The functions that run on the trait object would generally be generated using
 | 
|---|
| 111 | the following pattern:
 | 
|---|
| 112 | 
 | 
|---|
| 113 |     void draw(Surface & surface, drawable & traitObj) {
 | 
|---|
| 114 |         return traitObj.vtable->draw(surface, traitObj.object);
 | 
|---|
| 115 |     }
 | 
|---|
| 116 | 
 | 
|---|
| 117 | There may have to be special cases for things like copy construction, that
 | 
|---|
| 118 | might require a more sigificant wrapper. On the other hand moving could be
 | 
|---|
| 119 | implemented by moving the pointers without any need to refer to the base
 | 
|---|
| 120 | object.
 | 
|---|
| 121 | 
 | 
|---|
| 122 | ### Extention: Multiple Trait Parameters
 | 
|---|
| 123 | Currently, this gives traits two independent uses. They use the same syntax,
 | 
|---|
| 124 | except for limits boxable traits have, and yet don't really mix. The most
 | 
|---|
| 125 | natural way to do this is to allow trait instances to pick one parameter
 | 
|---|
| 126 | that they are generic over, the others they choose types to implement.
 | 
|---|
| 127 | 
 | 
|---|
| 128 | The two ways to do the selection, the first is do it at the trait definition.
 | 
|---|
| 129 | Each trait picks out a single parameter which it can box (here the `virtual`
 | 
|---|
| 130 | qualifier). When you create an instance of a trait object you provide
 | 
|---|
| 131 | arguments like for a generic structure, but skip over the marked parameter.
 | 
|---|
| 132 | 
 | 
|---|
| 133 |     trait combiner(virtual otype T, otype Combined) {
 | 
|---|
| 134 |         void combine(T &, Combined &);
 | 
|---|
| 135 |     }
 | 
|---|
| 136 | 
 | 
|---|
| 137 |     trait combiner(int) int_combiner;
 | 
|---|
| 138 | 
 | 
|---|
| 139 | The second is to do it at the instaniation point. A placeholder (here the
 | 
|---|
| 140 | keyword `virtual`) is used to explicately skip over the parameter that will be
 | 
|---|
| 141 | abstracted away, with the same rules as above if it was the marked parameter.
 | 
|---|
| 142 | 
 | 
|---|
| 143 |     trait combiner(otype T, otype Combined) {
 | 
|---|
| 144 |         void combine(T &, Combined &);
 | 
|---|
| 145 |     };
 | 
|---|
| 146 | 
 | 
|---|
| 147 |     trait combiner(virtual, int) int_combiner;
 | 
|---|
| 148 | 
 | 
|---|
| 149 | Using both (first to set the default, second as a local override) would also
 | 
|---|
| 150 | work, although might be exessively complicated.
 | 
|---|
| 151 | 
 | 
|---|
| 152 | This is useful in cases where you want to use a generic type, but leave part
 | 
|---|
| 153 | of it open and store partially generic result. As a simple example
 | 
|---|
| 154 | 
 | 
|---|
| 155 |     trait folder(otype T, otype In, otype Out) {
 | 
|---|
| 156 |         void fold(T & this, In);
 | 
|---|
| 157 |         Out fold_result(T & this);
 | 
|---|
| 158 |     }
 | 
|---|
| 159 | 
 | 
|---|
| 160 | Which allows you to fold values without putting them in a container. If they
 | 
|---|
| 161 | are already in a container this is exessive, but if they are generated over
 | 
|---|
| 162 | time this gives you a simple interface. This could for instance be used in
 | 
|---|
| 163 | a profile, where T changes for each profiling statistic and you can plug in
 | 
|---|
| 164 | multiple profilers for any run by adding them to an array.
 | 
|---|
| 165 | 
 | 
|---|
| 166 | Hierarchy
 | 
|---|
| 167 | ---------
 | 
|---|
| 168 | 
 | 
|---|
| 169 | Virtual tables by them selves are not quite enough to implement the planned
 | 
|---|
| 170 | hierarchy system. An addition of type ids, implemented as pointers which
 | 
|---|
| 171 | point to your parent's type id, is required to actually create the shape of
 | 
|---|
| 172 | the hierarchy. However vtables would allow behaviour to be carried with the
 | 
|---|
| 173 | tree.
 | 
|---|
| 174 | 
 | 
|---|
| 175 | The hierarchy would be a tree of types, of traits and structs. Currently we do
 | 
|---|
| 176 | not support structural extension, so traits form the internal nodes and
 | 
|---|
| 177 | structures the leaf nodes.
 | 
|---|
| 178 | 
 | 
|---|
| 179 | The syntax is undecided but it will include a clause like `virtual (PARENT)`
 | 
|---|
| 180 | on trait and struct definitions. It marks out all types in a hierarchy.
 | 
|---|
| 181 | PARENT may be omitted, if it is this type is the root of a hierarchy. Otherwise
 | 
|---|
| 182 | it is the name of the type that is this type's parent in the hierarchy.
 | 
|---|
| 183 | 
 | 
|---|
| 184 | Traits define a trait instance type that implements all assertions in this
 | 
|---|
| 185 | trait and its parents up until the root of the hierarchy. Each trait then
 | 
|---|
| 186 | defines a vtable type. Structures will also have a vtable type but it should
 | 
|---|
| 187 | be the same as their parent's.
 | 
|---|
| 188 | 
 | 
|---|
| 189 | Trait objects within the tree can be statically cast to a parent type. Casts
 | 
|---|
| 190 | from a parent type to a child type are conditional, they check to make sure
 | 
|---|
| 191 | the underlying instance is an instance of the child type, or an instance of
 | 
|---|
| 192 | one of its children. The type then is recoverable at run-time.
 | 
|---|
| 193 | 
 | 
|---|
| 194 | As with regular trait objects, calling a function on a trait object will cause
 | 
|---|
| 195 | a look-up on the the virtual table. The casting rules make sure anything that
 | 
|---|
| 196 | can be cast to a trait type will have all the function implementations for
 | 
|---|
| 197 | that trait.
 | 
|---|
| 198 | 
 | 
|---|
| 199 | Converting from a concrete type (structures at the edge of the hierarchy) to
 | 
|---|
| 200 | an abstract type works the same as with normal trait objects, the underlying
 | 
|---|
| 201 | object is packaged with a virtual table pointer. Converting back to an abstract
 | 
|---|
| 202 | type requires confirming the underlying type matches, but then simply extracts
 | 
|---|
| 203 | the pointer to it.
 | 
|---|
| 204 | 
 | 
|---|
| 205 | Exception Example:
 | 
|---|
| 206 | (Also I'm not sure where I got these casing rules.)
 | 
|---|
| 207 | 
 | 
|---|
| 208 |     trait exception(otype T) virtual() {
 | 
|---|
| 209 |         char const * what(T & this);
 | 
|---|
| 210 |     }
 | 
|---|
| 211 | 
 | 
|---|
| 212 |     trait io_error(otype T) virtual(exception) {
 | 
|---|
| 213 |         FILE * which_file(T & this);
 | 
|---|
| 214 |     }
 | 
|---|
| 215 | 
 | 
|---|
| 216 |     struct eof_error(otype T) virtual(io_error) {
 | 
|---|
| 217 |         FILE * file;
 | 
|---|
| 218 |     }
 | 
|---|
| 219 | 
 | 
|---|
| 220 |     char const * what(eof_error &) {
 | 
|---|
| 221 |         return "Tried to read from an empty file.";
 | 
|---|
| 222 |     }
 | 
|---|
| 223 | 
 | 
|---|
| 224 |     FILE * which_file(eof_error & this) {
 | 
|---|
| 225 |         return eof_error.file;
 | 
|---|
| 226 |     }
 | 
|---|
| 227 | 
 | 
|---|
| 228 | Ast Example:
 | 
|---|
| 229 | 
 | 
|---|
| 230 |     trait ast_node(otype T) virtual() {
 | 
|---|
| 231 |         void print(T & this, ostream & out);
 | 
|---|
| 232 |         void visit(T & this, Visitor & visitor);
 | 
|---|
| 233 |         CodeLocation const & get_code_location(T & this);
 | 
|---|
| 234 |     }
 | 
|---|
| 235 | 
 | 
|---|
| 236 |     trait expression_node(otype T) virtual(ast_node) {
 | 
|---|
| 237 |         Type eval_type(T const & this);
 | 
|---|
| 238 |     }
 | 
|---|
| 239 | 
 | 
|---|
| 240 |     struct operator_expression virtual(expression_node) {
 | 
|---|
| 241 |         enum operator_kind kind;
 | 
|---|
| 242 |         trait expression_node rands[2];
 | 
|---|
| 243 |     }
 | 
|---|
| 244 | 
 | 
|---|
| 245 |     trait statement_node(otype T) virtual(ast_node) {
 | 
|---|
| 246 |         vector(Label) & get_labels(T & this);
 | 
|---|
| 247 |     }
 | 
|---|
| 248 | 
 | 
|---|
| 249 |     struct goto_statement virtual(statement_node) {
 | 
|---|
| 250 |         vector(Label) labels;
 | 
|---|
| 251 |         Label target;
 | 
|---|
| 252 |     }
 | 
|---|
| 253 | 
 | 
|---|
| 254 |     trait declaration_node(otype T) virtual(ast_node) {
 | 
|---|
| 255 |         string name_of(T const & this);
 | 
|---|
| 256 |         Type type_of(T const & this);
 | 
|---|
| 257 |     }
 | 
|---|
| 258 | 
 | 
|---|
| 259 |     struct using_declaration virtual(declaration_node) {
 | 
|---|
| 260 |         string new_type;
 | 
|---|
| 261 |         Type old_type;
 | 
|---|
| 262 |     }
 | 
|---|
| 263 | 
 | 
|---|
| 264 |     struct variable_declaration virtual(declaration_node) {
 | 
|---|
| 265 |         string name;
 | 
|---|
| 266 |         Type type;
 | 
|---|
| 267 |     }
 | 
|---|
| 268 | 
 | 
|---|
| 269 | #### Sample Implementation
 | 
|---|
| 270 | The type id may be as little as:
 | 
|---|
| 271 | 
 | 
|---|
| 272 |     struct typeid {
 | 
|---|
| 273 |         struct typeid const * const parent;
 | 
|---|
| 274 |     };
 | 
|---|
| 275 | 
 | 
|---|
| 276 | Some linker magic would have to be used to ensure exactly one copy of each
 | 
|---|
| 277 | structure for each type exists in memory. There seem to be spectial once
 | 
|---|
| 278 | sections that support this and it should be easier than generating unique
 | 
|---|
| 279 | ids across compilation units.
 | 
|---|
| 280 | 
 | 
|---|
| 281 | The structure could be extended to contain any additional type information.
 | 
|---|
| 282 | 
 | 
|---|
| 283 | There are two general designs for vtables with type ids. The first is to put
 | 
|---|
| 284 | the type id at the top of the vtable, this is the most compact and efficient
 | 
|---|
| 285 | solution but only works if we have exactly 1 vtable for each type. The second
 | 
|---|
| 286 | is to put a pointer to the type id in each vtable. This has more overhead but
 | 
|---|
| 287 | allows multiple vtables.
 | 
|---|
| 288 | 
 | 
|---|
| 289 |     struct <trait>_vtable {
 | 
|---|
| 290 |         struct typeid const id;
 | 
|---|
| 291 | 
 | 
|---|
| 292 |         // Trait dependent list of vtable members.
 | 
|---|
| 293 |     };
 | 
|---|
| 294 | 
 | 
|---|
| 295 |     struct <trait>_vtable {
 | 
|---|
| 296 |         struct typeid const * const id;
 | 
|---|
| 297 | 
 | 
|---|
| 298 |         // Trait dependent list of vtable members.
 | 
|---|
| 299 |     };
 | 
|---|
| 300 | 
 | 
|---|
| 301 | ### Virtual Casts
 | 
|---|
| 302 | To convert from a pointer to a type higher on the hierarchy to one lower on
 | 
|---|
| 303 | the hierarchy a check is used to make sure that the underlying type is also
 | 
|---|
| 304 | of that lower type.
 | 
|---|
| 305 | 
 | 
|---|
| 306 | The proposed syntax for this is:
 | 
|---|
| 307 | 
 | 
|---|
| 308 |     trait SubType * new_value = (virtual trait SubType *)super_type;
 | 
|---|
| 309 | 
 | 
|---|
| 310 | It will return the same pointer if it does point to the subtype and null if
 | 
|---|
| 311 | it does not, doing the check and conversion in one operation.
 | 
|---|
| 312 | 
 | 
|---|
| 313 | ### Inline vtables
 | 
|---|
| 314 | Since the structures here are usually made to be turned into trait objects
 | 
|---|
| 315 | it might be worth it to have fields on them to store the virtual table
 | 
|---|
| 316 | pointer. This would have to be declared on the trait as an assertion (example:
 | 
|---|
| 317 | `vtable;` or `T.vtable;`), but if it is the trait object could be a single
 | 
|---|
| 318 | pointer.
 | 
|---|
| 319 | 
 | 
|---|
| 320 | There are also three options for where the pointer to the vtable. It could be
 | 
|---|
| 321 | anywhere, a fixed location for each trait or always at the front. For the per-
 | 
|---|
| 322 | trait solution an extention to specify what it is (example `vtable[0];`) which
 | 
|---|
| 323 | could also be used to combine it with others. So these options can be combined
 | 
|---|
| 324 | to allow access to all three options.
 | 
|---|
| 325 | 
 | 
|---|
| 326 | ### Virtual Tables as Types
 | 
|---|
| 327 | Here we consider encoding plus the implementation of functions on it to be a
 | 
|---|
| 328 | type. Which is to say in the type hierarchy structures aren't concrete types
 | 
|---|
| 329 | anymore, instead they are parent types to vtables, which combine the encoding
 | 
|---|
| 330 | and implementation.
 | 
|---|
| 331 | 
 | 
|---|
| 332 | Resolution Scope
 | 
|---|
| 333 | ----------------
 | 
|---|
| 334 | 
 | 
|---|
| 335 | What is the scope of a resolution? When are the functions in a vtable decided
 | 
|---|
| 336 | and how broadly is this applied?
 | 
|---|
| 337 | 
 | 
|---|
| 338 | ### Type Level:
 | 
|---|
| 339 | Each structure has a single resolution for all of the functions in the
 | 
|---|
| 340 | virtual trait. This is how many languages that implement this or similar
 | 
|---|
| 341 | features do it.
 | 
|---|
| 342 | 
 | 
|---|
| 343 | The main thing CFA would need to do it this way is some single point where
 | 
|---|
| 344 | the type declaration, including the functions that satisfy the trait, are
 | 
|---|
| 345 | all defined. Currently there are many points where this can happen, not all
 | 
|---|
| 346 | of them will have the same definitions and no way to select one over the
 | 
|---|
| 347 | other.
 | 
|---|
| 348 | 
 | 
|---|
| 349 | Some syntax would have to be added to specify the resolution point. To ensure
 | 
|---|
| 350 | a single instance there may have to be two variants, one forward declaration
 | 
|---|
| 351 | and one to create the instance. With some compiler magic the forward
 | 
|---|
| 352 | declaration maybe enough.
 | 
|---|
| 353 | 
 | 
|---|
| 354 |     extern trait combiner(struct summation) vtable;
 | 
|---|
| 355 |     trait combiner(struct summation) vtable;
 | 
|---|
| 356 | 
 | 
|---|
| 357 | Or (with the same variants):
 | 
|---|
| 358 | 
 | 
|---|
| 359 |     vtable combiner(struct summation);
 | 
|---|
| 360 | 
 | 
|---|
| 361 | The extern variant promises that the vtable will exist while the normal one
 | 
|---|
| 362 | is where the resolution actually happens.
 | 
|---|
| 363 | 
 | 
|---|
| 364 | ### Explicit Resolution Points:
 | 
|---|
| 365 | Slightly looser than the above, there are explicit points where the vtables
 | 
|---|
| 366 | are resolved, but there is no limit on the number of resolution points that
 | 
|---|
| 367 | might be provided. Each time a object is bound to a trait, one of the
 | 
|---|
| 368 | resolutions is selected. This might be the most flexible option.
 | 
|---|
| 369 | 
 | 
|---|
| 370 | An syntax would have to be provided as above. There may also be the option
 | 
|---|
| 371 | to name resolution points so that you can choose between them. This also
 | 
|---|
| 372 | could come with the ability to forward declare them.
 | 
|---|
| 373 | 
 | 
|---|
| 374 | Especially if they are not named, these resolution points should be able to
 | 
|---|
| 375 | appear in functions, where the scoping rules can be used to select one.
 | 
|---|
| 376 | However this also means that stack-allocated functions can end up in the
 | 
|---|
| 377 | vtable.
 | 
|---|
| 378 | 
 | 
|---|
| 379 |     extern trait combiner(struct summation) vtable sum;
 | 
|---|
| 380 |     trait combiner(struct summation) vtable sum;
 | 
|---|
| 381 | 
 | 
|---|
| 382 |     extern trait combiner(struct summation) vtable sum default;
 | 
|---|
| 383 |     trait combiner(struct summation) vtable sum default;
 | 
|---|
| 384 | 
 | 
|---|
| 385 | The extern difference is the same before. The name (sum in the samples) is
 | 
|---|
| 386 | used at the binding site to say which one is picked. The default keyword can
 | 
|---|
| 387 | be used in only some of the declarations.
 | 
|---|
| 388 | 
 | 
|---|
| 389 |     trait combiner fee = (summation_instance, sum);
 | 
|---|
| 390 |     trait combiner foe = summation_instance;
 | 
|---|
| 391 | 
 | 
|---|
| 392 | (I am not really happy about this syntax, but it kind of works.)
 | 
|---|
| 393 | The object being bound is required. The name of the vtable is optional if
 | 
|---|
| 394 | there is exactly one vtable name marked with default.
 | 
|---|
| 395 | 
 | 
|---|
| 396 | These could also be placed inside functions. In which case both the name and
 | 
|---|
| 397 | the default keyword might be optional. If the name is ommited in an assignment
 | 
|---|
| 398 | the closest vtable is choosen (returning to the global default rule if no
 | 
|---|
| 399 | approprate local vtable is in scope).
 | 
|---|
| 400 | 
 | 
|---|
| 401 | ### Site Based Resolution:
 | 
|---|
| 402 | Every place in code where the binding of a vtable to an object occurs has
 | 
|---|
| 403 | its own resolution. Syntax-wise this is the simplest as it should be able
 | 
|---|
| 404 | to use just the existing declarations and the conversion to trait object.
 | 
|---|
| 405 | It also is very close to the current polymorphic resolution rules.
 | 
|---|
| 406 | 
 | 
|---|
| 407 | This works as the explicit resolution points except the resolution points
 | 
|---|
| 408 | are implicit and their would be no selection of which resolution to use. The
 | 
|---|
| 409 | closest (current) resolution is always selected.
 | 
|---|
| 410 | 
 | 
|---|
| 411 | This could easily lead to an explosion of vtables as it has the most fine
 | 
|---|
| 412 | grained resolution the number of bindings in a single scope (that produces
 | 
|---|
| 413 | the same binding) could be quite high. Merging identical vtables might help
 | 
|---|
| 414 | reduce that.
 | 
|---|
| 415 | 
 | 
|---|
| 416 | Vtable Lifetime Issues
 | 
|---|
| 417 | ----------------------
 | 
|---|
| 418 | 
 | 
|---|
| 419 | Vtables interact badly with the thunk issue. Conceptually vtables are static
 | 
|---|
| 420 | like type/function data they carry, as those decisions are made by the
 | 
|---|
| 421 | resolver at compile time.
 | 
|---|
| 422 | 
 | 
|---|
| 423 | Stack allocated functions interact badly with this because they are not
 | 
|---|
| 424 | static. There are several ways to try to resolve this, however without a
 | 
|---|
| 425 | general solution most can only buy time.
 | 
|---|
| 426 | 
 | 
|---|
| 427 | Filling in some fields of a static vtable could cause issues on a recursive
 | 
|---|
| 428 | call. And then we are still limited by the lifetime of the stack functions, as
 | 
|---|
| 429 | the vtable with stale pointers is still a problem.
 | 
|---|
| 430 | 
 | 
|---|
| 431 | Dynamically allocated vtables introduces memory management overhead and
 | 
|---|
| 432 | requires some way to differentiate between dynamic and statically allocated
 | 
|---|
| 433 | tables. The stale function pointer problem continues unless those becomes
 | 
|---|
| 434 | dynamically allocated as well which gives us the same costs again.
 | 
|---|
| 435 | 
 | 
|---|
| 436 | Stack allocating the vtable seems like the best issue. The vtable's lifetime
 | 
|---|
| 437 | is now the limiting factor but it should be effectively the same as the
 | 
|---|
| 438 | shortest lifetime of a function assigned to it. However this still limits the
 | 
|---|
| 439 | lifetime "implicitly" and returns to the original problem with thunks.
 | 
|---|