Hi,
I come from go and functional languages.
And there is things that I feel it is not clearly addressed in most tutorial/blogs despite their good qualities. (at east for someone like me)
If you cannot afford to simply switch over multiple structs, you end up with 3 “entities”:
-
implementation(s):
- struct that share have a specific method/data patterns (ex:
impl.next(self: Impl)
- struct that share have a specific method/data patterns (ex:
-
an interface (VTable)
- a reference to an struct implementation (pointer to its data and the needed methods operating on it)
- with all the necessary mechanism to do the linking and typecasting
- and eventually other quality of life functions that rely on the implementation’s ones (that way you can simply write it one in the interface instead of x times in each implementation)
- ex: link_space = struct {
pointer_to_whatever_data: *anyopaque,
pointer_to_whatever_next_method: *fn(anyopaque)Something,
fn link_everything( …
}
-
a final caller, that need to call the interface and cannot be bother about listing all possible implementations.
- ex : fn(whatever_interface: WhateverInterface)Something {…
Is it right to think that from that point, you can freely choose :
- to have the interface logic to be embedded into the interface caller struct
- to have the implementation to beget the interface
- like the std random
- or keep all three separated ?
and then it depends on if you will switch from one implementation to another often at runtime , if multiple final callers will call the same implementation or maybe only one final caller will switch over different implementations at runtime or …
“random” example
var random_implementaton = std.Random.DefaultPrng.init(42);
const random_interface = random_implementaton.random();
need_random_interface( &random_interface );
could have been:
var rand_implementaton = std.Random.DefaultPrng.init(42);
const random_interface = RandomInterface.newFromImplementation(
random_implementation,
);
need_random_interface( &random_interface );
?
or:
var random_interface = RandomInterface.new();
var random_implementaton = std.Random.DefaultPrng.init(42);
random_interface.linkTo(random_implementaton);
need_random_interface( &random_interface );
?
or:
var random_interface = RandomInterface.comptimeNew(.DefaultPrng);
need_random_interface( &random_interface );
?
and what would be tempted to do:
var random_implementaton = std.Random.DefaultPrng.init(42);
need_random_interface_internally.linkRandomImplementation(&random_implementaton);
_ =
need_random_interface_internally.something_that_do_random_stuff();
?
(sorry, I probably made mistakes about what should be var/const)
Intuitively, I think it is weird that the concept of the general interface to be part of the implementation.
Generic “entities” should be aware of the different implementations not the other way around but I would expect practical use cases to defeat it
Is there legacy idioms from C/C++ that justify doing that way?
Or the expected practical usage pattern that make it de facto the right choice?
Pattern in the standard library is “standard library pattern”, different contexts involve different patterns.
Or “std lib writer had to choose one they picked that, there is no perfect solution, just deal with it” ?
Sorry if it has already been asked could not find it.