Thanks to SkiFire13 and RalfJung for bringing up the scoped-tls-hkt / make_static issues!
However, I think there might still be a way to get a weaker version of this Cell to work, basically by replacing Untainted with const.
The core security guarantee is that access is never allowed to call itself in the callback.
Now, in a const fn we are not allowed to call non-const functions. Which means that if we force our callback to be a const fn (while access is not) it can never call access a second time.
This is sadly more restrictive, because
- const functions are very restricted (eg. no heap allocations yet)
- We can't have different brands of taintedness, there's just one - 'const'
Here's a rough implementation:
Code
#![feature(const_trait_impl, effects)]
use std::cell::UnsafeCell;
// A hack to simulate `const FnOnce(T) -> R`
#[const_trait] trait ConstCall<T, R> {
fn const_call(self, t: T) -> R;
}
struct UnconstCell<T> {
cell: UnsafeCell<T>,
}
impl<T> UnconstCell<T> {
fn new(t: T) -> UnconstCell<T> {
UnconstCell {
cell: UnsafeCell::new(t)
}
}
fn access<R, F>(&self, f: F) -> R
where F: for<'a> ConstCall<&'a mut T, R>,
F: for<'a> const ConstCall<&'a mut T, R>
{
let r = unsafe { &mut *self.cell.get() };
f.const_call(r)
}
}
Example Usage
struct MyVec {
// no const heap allocations thus far. :/
data: UnconstCell<[i32; 64]>,
}
// Note that this API does not require "const"! And accepts "&self".
impl MyVec {
pub fn new(data: [i32; 64]) -> MyVec {
MyVec { data: UnconstCell::new(data) }
}
pub fn set(&self, i: usize, v: i32) {
self.data.access(SetCall(i, v))
}
pub fn get(&self, i: usize) -> i32 {
self.data.access(GetCall(i))
}
}
struct SetCall(usize, i32);
impl<'a> const ConstCall<&'a mut [i32; 64], ()> for SetCall {
fn const_call(self, arg: &mut [i32; 64]) -> () {
let SetCall(i, v) = self;
arg[i] = v;
}
}
struct GetCall(usize);
impl<'a> const ConstCall<&'a mut [i32; 64], i32> for GetCall {
fn const_call(self, arg: &mut [i32; 64]) -> i32 {
let GetCall(i) = self;
return arg[i];
}
}
fn main() {
let x = MyVec::new([0; 64]);
x.set(3, 5);
dbg!(x.get(3));
}
(sorry for the raw code, I can't reach the Rust Playground right now)