Rather than checking the size of the struct, couldn't you simply try to just make statefulness ineffective?
Just to throw an idea out there:
pub struct VTable {
pub foo: fn(&A) -> &B,
pub bar: fn(C, C, C) -> Electro<'static>,
...
}
pub trait Trait {
fn vtable(&self) -> VTable;
}
Now, of course, somebody could still sneak state in there by e.g. having a RefCell<bool>
and putting different function pointers in the VTable based on that flag. Perhaps you have additional constraints in your situation that may make this a non-issue; but if it is a problem, maybe we could go one step further:
Well, okay. This next idea is a fair bit more than "one step further." But hear me out. This is basically a proof of concept for using two traits to simulate object-safe static methods.
The idea is:
- Make it impossible for anyone to implement Trait outside the crate or module where it is defined.
- Have a second trait,
TraitImpl
which is the one people implement. This trait doesn't have to be object safe. Either give it all static methods, or require people to implement it on a unit-like type of your choosing.
I'll assume your implementors are in a different crate from the trait. (they don't have to be)
upstream/lib.rs
/// The trait you *implement*.
/// However, this isn't the trait you *use*. See Trait.
pub trait TraitImpl: Sized {
fn foo(u32) -> i32;
fn boxing_day(Vec<u64>) -> Box<Box<Box<&'static [((), u64)]>>>;
// fn ...
}
/// A type that you can't construct. Ha ha!
///
/// This prohibits you from writing your own impl of Trait outside this crate.
pub struct Token(());
/// The trait you use to make a trait object from TraitImpl.
pub trait Trait {
fn foo(&self, x: u32) -> (Token, i32);
fn boxing_day(&self, x: Vec<u64>) -> (Token, Box<Box<Box<&'static [((), u64)]>>>);
// fn ...
}
// Generic impl of TraitObj that forwards to 'impl Trait<Type> for GreatJustice<Type>' impls.
// Any other impl besides this one is only capable of panicking.
impl<A: TraitImpl> Trait for A {
fn foo(&self, x: u32) -> (Token, i32) {
(Token(()), <A as TraitImpl>::foo(x))
}
fn boxing_day(&self, x: Vec<u64>) -> (Token, Box<Box<Box<&'static [((), u64)]>>>) {
(Token(()), <A as TraitImpl>::boxing_day(x))
}
}
downstream/main.rs
extern crate upstream;
use upstream::{TraitImpl, Trait};
struct Struct;
impl TraitImpl for Struct {
fn foo(x: u32) -> i32 {
(x * 2) as _
}
fn boxing_day(_: Vec<u64>) -> Box<Box<Box<&'static [((), (u64))]>>> {
Box::new(Box::new(Box::new(&[])))
}
}
fn main() {
let _: &Trait = &Struct;
}
Edit: Okay, technically this still has a loophole in that you can write an impl of Trait by calling the methods of Trait defined on another type to receive a Privileged
value. I need a logician!
Edit: Vastly simplified the implementation. Loophole is still present.