rune icon indicating copy to clipboard operation
rune copied to clipboard

feature: runtime traits to represent protocols

Open udoprog opened this issue 5 years ago • 2 comments

We currently have "protocols" to implement various functions. These are really just free instance functions with custom name hashes that can't be named dynamically.

This means that protocol impls currently can't be defined dynamically. So there is no way to make this run (which requires the STRING_DISPLAY protocol):

struct Foo;

fn main() {
    let foo = Foo;
    println(`{foo}`);
}

It should be possible to add "runtime traits" to Rune to represent these protocols. To allow for the following:

struct Foo;

impl std::fmt::Display for Foo {
    fn fmt(self, f) {
        write(f, "Foo as a string")
    }
}

fn main() {
    let foo = Foo;
    println(`{foo}`); // -> "Foo as a string"
}

udoprog avatar Sep 07 '20 04:09 udoprog

@udoprog I started thinking about this last night from a "native" side, since I want to emulate Deref for my generic structs - I've got something like

#[derive(Any)]
struct DerefProxy<T>
where
    T: runestick::Any,
{
    inner: AtomicRefMut<'static, T>, 
}

And this wrapper (whose sole purpose is to keep the lock alive) should be Deref<Target = T> just like the inner value is. I'm not sure about exact patterns to make it ergonomic and safe -- I guess there'll need to be some form of "cook" once all traits etc have been registered to generate a dispatch table. My current working idea for surface API is that there'll be something like this:

struct TraitApi { /* todo */ }
trait InstallTrait { /* todo */ }

impl<T> InstallTrait for T where T: std::fmt::Debug {
    fn api() -> TraitApi { ... }
    
    fn install(m: &mut Module) -> ... {
         m.inst_trait("std::fmt::Debug",
             Self::api().inst_fn("fmt", |s: &T, f: &mut String| { ... })
    }
}

Where each Module::inst_trait call is one segment of a virtual table. And then e.g STRING_DEBUG would be vm.dispatch("std::fmt::Debug", "fmt", target, args) or something like that. Haven't thought too much about this from the compiler perspective though, I guess there needs to be some form of resolution in untyped code -- e.g., how'd one differentiate between mutable/non-mutable access, etc, or simply name clashes between unrelated generic names (get, set, update, fetch, run, ...). You know more about the compiler than I do so I'd love to hear your thoughts on that side.

tgolsson avatar Jun 07 '21 07:06 tgolsson

Started hacking on the compiler side just to see where it slots it on and where it'll land in the final data. Progress branch here: https://github.com/tgolsson/rune/tree/ts/traits.

This is the code that I want to get to compile, ~ish, as a proof of concept.

trait MyTrait {
    fn echo(self);
    fn name(self) {
        "Derek"
    }
}

struct MyStruct { a }

impl MyTrait for MyStruct {
    fn echo(self) { MyTrait::name(self) }
}

pub fn main() {
    let m = MyStruct { a: 10 };
    m.echo()    
}

tgolsson avatar Jun 07 '21 21:06 tgolsson