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)