As I've gotten more and more into Rust (and more and more into type theory), I've noticed that inversion of control (IoC) makes the Rust type system significantly more expressive, but at a huge cost to ergonomics. For example, it's well known that Rust lacks proper support for linear types. However, with IoC, we can actually implement them like so. Another use of IoC is the "branded type" pattern, as seen in GhostCell (described here and implemented here). However, in both of these examples, the ergonomics are really lacking, since the user ends up having to write many potentially nested closures. Of course, macros can help with this, but macros have their own ergonomics issues. Variadic generics might also alleviate some of the closure nesting if and when they're implemented.
So, I was wondering:
Are there any existing proposals to address this issue?
Is there an IoC work-around I'm not seeing?
Would it be worth it to try to come up with a solution myself?
That's certainly relevant! I quite like that idea, but I don't think it solves the ergonomics issues for branded types. I also worry it's unlikely to actually be accepted, since new auto traits seem unpopular. (I'm pretty new though, so correct me if I'm wrong.)
Branded types are a design pattern described in the GhostCell paper I linked that can be used to implement singletons. A naive implementation of singletons in Rust might look like:
let x = Singleton::<'static>::new();
let y = Singleton::<'static>::new();
which would break the singleton invariant. (Even without the turbofish, the compiler could decide that x and y have the same type due to how unbounded lifetimes work.) We can fix our singleton by replacing the new method with the following:
Now, in the context of the user-provided closure, the compiler can assume nothing about the lifetime of the input singleton. From the user's perspective, no two singletons can ever have the same lifetime. (This is actually proven formally in the GhostCell paper!) This design pattern of using an invariant marker lifetime combined with a constructor that executes a closure that is generic over said lifetime is the branded type pattern.
Oh, I probably should have checked whose blog that was lol