When defining a trait, one can mark it as unsafe
to indicate that "implementors of this trait must uphold certain invariants":
unsafe trait Trait {
// Unsafe to implement, safe to call.
fn foo(&self);
}
However, it's not always the case that all methods one has to implement are unsafe. For example, bar
could be perfectly safe to implement while foo
isn't. Alas, the unsafe trait
taints the entire implementation:
unsafe trait Trait {
// Unsafe to implement, safe to call.
fn foo(&self);
// This is actually safe to implement, but it's still inside `unsafe trait` :-(
fn bar(&self);
}
One option is to split the trait into two:
unsafe trait UnsafeTrait {
fn foo(&self);
}
trait SafeTrait {
fn bar(&self);
}
trait Trait: UnsafeTrait + SafeTrait {}
impl<T: UnsafeTrait + SafeTrait> Trait for T {}
I propose extending unsafe
similar to how the visibility modifier pub
can be controlled further, such as pub(crate)
. The idea here is introducing unsafe(impl)
:
trait Trait {
unsafe(impl) fn foo(&self);
fn bar(&self);
}
When users want to call Trait::foo
, they do not need to be in an unsafe
context, but implementors do need to specify unsafe fn
:
impl Trait for i32 {
unsafe(impl) fn foo(&self) {}
fn bar(&self) {}
}
fn qux<T: Trait>(t: T) {
t.foo(); // <- safe to call
}
No new keywords are needed, because impl
fits the semantics perfectly.
Note: I do not actually think this would be a worthwhile addition. In my opinion, the "tainting" isn't so bad (at least for relatively small traits), and one can always go the split route (although implementing it becomes a bit more annoying). However, I just came across this scenario, and I wanted to share my thoughts on "what if".
What do you all think of this idea?