Shizcow / hotpatch

Changing function definitions at runtime in Rust
Apache License 2.0
129 stars 6 forks source link

Tracking Issue: Free Functions #7

Closed Shizcow closed 3 years ago

Shizcow commented 3 years ago

This issue is meant to track the status of patching methods, as well as a public idea board. This is sectioned mainly into two achievements: associated functions and methods. NOTE methods will get their own tracking issue; this will be closed when associated functions are done.

TODO items for associated functions:


Associated Functions

Prior to filing this issue, a method of patchable associated functions was discovered. The final implementation syntax is as follows:

struct Foo {}

#[patchable]
impl Foo {
  fn bar() {}
}

fn main() {
  Foo::bar.hotpatch_fn(|| unimplemented!());
}

It is (roughly) implemented internally as follows:

struct Foo {}
static __hotpatch_implgen_Foo_bar: hotpatch::Patchable<(Self, i32), ()> =
    hotpatch::Patchable::__new( ||
        // macro passthrough from the initial implementation
    );
fn __hotpatch_staticwrap_0_bar() -> &'static Patchable<(Self, i32), ()> {
    &__hotpatch_implgen_Foo_bar
}
impl Foo {
    pub const bar: hotpatch::MutConst<Patchable<(Self, i32), ()>> =
        hotpatch::MutConst::new(__hotpatch_staticwrap_0_bar);
     // where MutConst is a const reference to a static variable
     // It's pretty much an associated static with extra steps (they aren't supported)
}

Problem: Because the implementation must be separated from the impl block, this cannot support a self parameter (in any form).

Problem: Self is not available. I have yet to find a workaround, but I suspect one exists using dummy consts.


Methods

Recently I discovered a way of making patchable methods. The main holdup was declaring a way of having some associated static holding a function pointer with a self parameter. And the only way to get a self parameter to work is with a method directly in an impl block.

Or at least, I thought so. Traits perfectly achieve this. Combining this with the fact that there can be multiple struct associations under the same name if they're in a trait and of different types yields a perfect solution. The working syntax is as follows:

struct Foo {}

#[patchable]
impl Foo {
  fn bar(&self) {}
}

fn main() {
  let foo = Foo::new(); // new defined elsewhere
  foo.bar(); // works

  // hotpatched as the following:
  Foo::bar.hotpatch_fn(|| unimplemented!());

  // Yet the following does NOT work for hotpatching:
  foo.bar.hotpatch_fn(|| unimplemented!());
  // This is GREAT for cleanliness
}

The macro-expanded implementation is still being figured out. However, the following is a working demo of making Foo::bar different from foo.bar:

struct Foo {
    data: i32,
}

impl Foo {
    fn new(data: i32) -> Self {
    Self{data}
    }
}

// begin macro generated content

// macro input:
// #[patchable]
// impl Foo {
//     fn bar(self) {
//         println!("hello from bar! This foo object has data: {}", self.data);
//     }
// }

impl Foo {
    #[allow(non_upper_case_globals)]
    const bar: i32 = 0;
}

trait FnTrait { // final implementation will use mangled names
    fn bar(self);
}

impl FnTrait for Foo {
    fn bar(self) {
    println!("hello from bar! This foo object has data: {}", self.data);
    }
}

// end macro generated content

// test
fn main() {
    let foo = Foo::new(100); // just gives foo some instance data
    foo.bar(); // Has access to self, and the data  -^-----------^
    println!("{:?}", Foo::bar); // Foo::bar is a const i32; totally different!
}

I really like the fact that foo.bar is actually a method, and that foo.bar.hotpatch isn't allowed. Forcing the use of Foo::bar.hotpatch cements the idea that the method is patched for all instances.

Problem: trait associated functions (without self) are not privy to the same non-competition rules. This may mean a macro may need to decide whether to use the associated function implementation or the method implementation. This feels like a rustc bug to me.

Shizcow commented 3 years ago

Took a sidequest for fn_ptr branch. This fixes an intermediate issue I was having with incorrect lifetime elision on patchable types. This should be good to go next.