After thinking a bit more about my proposal for "capabilities" emulation through NamedCx
, I realize that my solution is terribly unsound.
I assume that I can uphold an invariant that all NamedX
objects with the same namespace ocurring in a CxRaw
will have the same pointee type, making it sound to write:
struct MyNamespace;
fn consumer<T, V>(cx: Cx<NamedMut<'_, MyNamespace, T>, NamedMut<'_, MyNamespace, V>>) {
let value: &mut T = cx;
let value: &mut V = cx;
// This should be sound because `T` and `V` should have been specified as
// the same type in order to construct `CxRaw`.
}
Unfortunately, while this invariant is easy to uphold on its own, it breaks down when combining the feature with AnyCx
merging:
fn merge<L: AnyCx, R: AnyCx>(left: L, right: R) -> Cx<L, R> {
// Previously, I asserted that this would work because distinct generic parameters
// are assumed not to alias.
Cx::new((left, right))
}
fn caller(cx_a: CxMut<NamedMut<'_, MyNamespace, *mut u32>>, cx_b: CxMut<NamedMut<'_, MyNamespace, Box<u32>>>) {
// Unfortunately, this allows us to break our oh-so-important invariant.
let merged = merge(cx_a, cx_b);
// Oh no! We just transmuted some random `*mut u32` into a `Box<u32>`!
consumer::<u32, i32>(merged);
}
Luckily, there's a much easier way to solve the original problem: use the tried-and-true bundle type technique. Best of all, it doesn't require any complex additions to the existing proposal!
trait HasLogger {
type Logger: Logger;
}
trait HasDatabase {
type Database: Database;
}
trait HasTracing {
type Tracing: Tracing;
}
trait HasBundleForCx1: HasLogger + HasDatabase {}
type Cx1<'a, B> = Cx<
&'a mut <B as HasLogger>::Logger>,
&'a mut <B as HasDatabase>::Database,
>;
trait HasBundleForCx2: HasLogger + HasTracing {}
type Cx2<'a, B> = Cx<
&'a mut <B as HasLogger>::Logger,
&'a mut <B as HasDatabase>::Tracing,
>;
type ParentCx<'a, B> = Cx<Cx1<'a, B>, Cx2<'a, B>>;
trait HasBundleForParent: HasBundleForCx1 + HasBundleForCx2 {}
fn consumer<B: ?Sized + HasBundleForParent>(cx: ParentCx<'_, B>) {
let logger: &mut B::Logger = cx;
let tracing &mut B::Tracing = cx;
// This already works because of the no-alias assumptions.
let _ = (logger, tracing);
}
fn caller(cx: Cx<&mut MyLogger, &mut MyDatabase, &mut MyTracing>) {
consumer::<
// We'll likely have to augment `Cx`'s inference system to allow
// this to be inferred automatically.
dyn HasBundleForParent<
Logger = MyLogger,
Database = MyDatabase,
Tracing = MyTracing,
>,
>(cx);
}
I have no clue what I was thinking earlier today.