IIRC, Rust in the Linux kernel is using a related but distinct trick. (Unfortunately I don't recall how to find a link to give an actual example.) Specifically, they have a system to do something like
#[vtable]
pub trait Ops {
fn spam(&self) -> Result { Err(Unsupported) }
fn eggs(&self) -> Result { Err(Unsupported) }
}
#[vtable]
impl Ops for Foo {
fn spam(&self) -> Result { /* ... */ }
}
expanding to
pub trait Ops {
const USE_VTABLE_ATTR: ();
const HAS_SPAM: bool = false;
fn spam(&self) -> Result { Err(Unsupported) }
const HAS_EGGS: bool = false;
fn eggs(&self) -> Result { Err(Unsupported) }
}
impl Ops for Foo {
const USE_VTABLE_ATTR: () = ();
const HAS_SPAM: bool = true;
fn spam(&self) -> Result { /* ... */ }
}
intended for use with FFI vtables such as (roughly, shorthand)
#[repr(C)]
#[derive(Default)]
pub struct OpsVtable {
pub spam: Option<unsafe fn(*const c_void) -> Result>;
pub eggs: Option<unsafe fn(*const c_void) -> Result>;
}
impl OpsVtable::new<T: Sized>() -> Self
where T: Ops {
Self {
spam: if !T::HAS_SPAM { None }
else { Some(|this| (&*this.cast::<T>()).spam()) },
eggs: if !T::HAS_EGGS { None }
else { Some(|this| (&*this.cast::<T>()).eggs()) },
}
}
where null
/None
is used as a marker for methods which aren't provided and should get the default behavior (as opposed to every provider setting the vtable field to the default behavior if necessary).
Optional trait methods which are optional as in Option
rather than just defaulted have interested me for a while. Utilizing fn
pointers is probably sufficient since LLVM can devirtualize an indirect call to constant function pointer relatively easily, but it does necessitate the use of function call syntax rather than method call syntax unless combined with some sort of fnptr supporting UMCS (receiver.(spam)()
?) or much more involved flow typing feature for whether the method is tested to exist.
They can't ever be exactly alike, because the Foo::const F
is a function pointer (pointer sized/aligned) whereas the Foo::fn f
is a function item (zero sized/aligned). It's also unlikely that the const
will ever be able to be invoked with method syntax because of the difference between fn(this: &Self)
and fn(self: &Self)
.
But the general concept of treating fn
items more like const
items certainly is interesting, especially w.r.t. how it gives applying FRU syntax an obvious meaning, even if that obvious meaning doesn't actually quite work (because of the deliberate limits on type-adjusting FRU) without applying further adjustments.
The "full send" version of running with the idea gives
pub trait Ops {
fn spam(&self);
fn eggs(&self);
}
semantics that could roughly be explained as
pub trait Ops {
type [fn spam]: FnItem(&Self);
type [fn eggs]: FnItem(&Self);
const impl: struct {
#[method] spam: [fn spam],
#[method] eggs: [fn eggs],
};
}
trait FnItem(Args...) -> Ret
= (Fn(Args...) -> Ret) + Default + Sized
+ CoerceInto<fn(Args...) -> Ret>
+ const { size_of::<Self>() == 0 };
pub trait dyn Ops = Ops<
[fn spam] = fn(&Self),
[fn eggs] = fn(&Self),
>; // despite fn(): !FnItem()