rune icon indicating copy to clipboard operation
rune copied to clipboard

Use dynamically dispatched generics to reduce namespace contamination

Open udoprog opened this issue 3 years ago • 1 comments

Come up with a generics scheme that works at runtime and allows for:

  • Dynamically dispatching a function call to the correct implementation depending on the type of the arguments.
  • Directly address the exact generic implementation at compile-time using generic function-call syntax (e.g. std::parse::<i64>(string)).

Suggested implementation details

The idea is that native generic functions are monomorphized at registration time. Take:

str::parse::<int>("42");
"1,2,3,4".split::<char>(',');
"1,2,3,4".split::<str>("2,3"); // Note: different argument type, different function impl is needed because its a different kind of pattern.

It would actually look up a function named "parse", who's hash includes the int type constructor. Programmatically this would be something like:

// NB: this is the regular parse function hash.
let parse_fn_hash = Hash::type_hash(&["parse"]);
Hash::type_hash_generic(parse_fn_hash, (i64::type_hash(),))

So what happens if we call the function generically?

This will error, since there's no generic type information available. We don't know which str::parse impl to pick:

str::parse("42");

The above case has to be specified at compile time, because the type of the argument doesn't determine the implementation to use:

str::parse::<i64>("42");

For case where we can look at the type of the argument (like String::split), the generic function by first looking up the split instance fn, then using the metadata to resolve all generic parameters and notice that they are input arguments, so it can generate the necessary type hash:

"1,2,3,4".split(',');

Or can be used more explicitly to avoid the dynamic dispatch:

"1,2,3,4".split::<char>(',');

udoprog avatar Dec 01 '20 08:12 udoprog

This would probably also improve situations where one wants to support more than one type and currently needs to accept Value:

impl Path {
    // should support `Path` and `String`
    fn eq(&self, path: Value) -> VmResult<bool> {
        let path = match path {
            string @ (Value::StaticString(_) | Value::String(_)) => {
                Path::from_str(match String::from_value(string) {
                    VmResult::Ok(o) => o,
                    VmResult::Err(e) => return VmResult::Err(e),
                })
            }
            any @ Value::Any(_) => match <Ref<Path>>::from_value(any) {
                VmResult::Ok(o) => o.to_owned(),
                VmResult::Err(e) => return VmResult::Err(e),
            },
            other => {
                return VmResult::panic(format!(
                    "Expected `Path` or `String` type, but found `{}`",
                    unwrap!(other.type_info())
                ));
            }
        };
        VmResult::Ok(self == &path)
    }
}

ModProg avatar Jun 01 '23 17:06 ModProg