Idea: partially object-safe traits

Imagine the following trait:

pub trait Foo {
    type Error: std::error::Error;
    
    fn foo(&mut self) -> Result<u32, Self::Error>;
    fn foo_boxed(&mut self) -> Result<u32, Box<dyn std::error::Error>>;
}

This trait is obviously not object safe. But it easily can be, if we are to allow only use of object safe methods like foo_boxed. We can work around this by introducing additional traits like DynFoo with a blanket impl for T: Foo, but it's somewhat annoying and increases API surface, especially if number of object safe methods is big.

To prevent potential confusion, partial object-safety could be made an opt-in thing. For example, it could look like this:

#[partial_object_safety]
pub trait Foo {
    type Error: std::error::Error;
    
    fn foo(&mut self) -> Result<u32, Self::Error>;
    fn foo_boxed(&mut self) -> Result<u32, Box<dyn std::error::Error>>;
}

fn use_foo(f: &mut dyn Foo) -> u32 {
    // we can only use `foo_boxed` on `f`,
    // trying to use `foo` will cause compilation error
    f.foo_boxed().unwrap()
}

(NOT A CONTRIBUTION)

Partially object-safe traits already exist: for associated functions that don't meet the object safety requirements, they can be excluded from the object type by adding where Self: Sized and the trait is now object safe.

There's no support for making a trait with associated types object safe without specifying the associated type, but that could probably be supported by allowing users to add where Self: Sized to the associated type and any associated function that uses it.

5 Likes

IMO where Self: Sized is a hack and not a proper solution. It works, until it doesn't because you want to implement your trait for str or [T]. It's also extremely unintuitive to beginners.

8 Likes

An "interesting idea" I've had is to do this via "manual dyn", i.e. you write impl Trait for dyn Trait yourself instead of the impl being synthesized for you. If we provide some way to still provide object-safe methods in the vtable (e.g. automatically (i.e. as a default method body, only need to impl the others) or explicitly opt-in (e.g. dyn fn)), then you can impl the not object safe parts from the object safe parts.

3 Likes

Related: this comment and the ones that follow (you'll have to "load more hidden items").


I've also wanted a NotDyn where Sized: NotDyn to replace the Sized hack for approximately forever, so I could implement non-dyn-dispatchable methods when the implementor is non-Sized.

1 Like