Non-callable closures

I actually mean "call-control", that is, to mark closures that should only be called by specific functions, closures, blocks, etc.

This idea is inspired by how Linux file permissions work. Rust already supports controlling read and write access of values, but there's no way to control execution.

This is useful if a closure should only be passed around, until it "reaches" the actual caller. It must not be called during its "path/journey" to the destination, otherwise it's a compilation error.

Non-callable closures can still be called in unsafe blocks.

I don't know exactly how this should be implemented externally. It could be an attribute, a keyword/modifier with parameters, a noexec block, etc...

Note that if you own a value, you can mutate it. Additionally, &references aren't always read-only, as types can expose shared mutability behind shared &references; this is how locks and atomics function.

You can do so as much as you can control write access, because you can write a type which only provides &mut functionality. That said, this isn't sufficient to make a value dormant until it reaches some target scope, because if you own a value, you can &mut it, and you can't access a value by &mut after passing it down by &.


If you control the calling location, you can accomplish this via privacy tricks.

// somewhere
pub struct Sealed<P, K> {
    payload: P,
    key: PhantomData<fn(K) -> P>,
}

impl<P, K> Sealed<P, K> {
    pub fn seal(payload: P) -> Self {
        Self { payload, key: PhantomData }
    }

    pub fn unseal(self, key: &K) -> P {
        self.payload
    }
}

// calling location
pub struct CallLocation {
    _priv: (),
}

impl CallLocation {
    // only constructable where you want to use the payload
    /* priv */ const fn get() -> Self {
        Self { _priv: () }
    }
}

fn do_stuff<F: FnOnce()>(f: Sealed<F, CallLocation>) {
    (f.unseal(&CallLocation::get()))()
}

// source location
do_stuff(Sealed::<_, CallLocation>::seal(|| {
    // …
})
1 Like

Always? I mean, I know unsafe can be used to bypass immutability, but I didn't know it was possible without unsafe.

Oh... I guess my proposal may not be a good idea. Thanks for the info anyways! I'm learning a lot

True. I made a mistake in my post. I should've said "As far as I know". I didn't gave it enough thought (sorry for my bad English, I couldn't think of a better sentence)

Ohh... now I get it. Thanks again!

You're probably confusing immutable/mutable references with immutable/mutable bindings. You can't convert immutable references to mutable ones, but you can always move a value from an immutable binding to a mutable one. For example:

let a = Vec::new();
a.push(1); // doesn't compile, the binding is immutable
let mut b = a; // ownership of a moved to mutable binding b
b.push(2); // compiles, the binding is mutable

I think the better way to do this is to take a "key" type as an argument to said closure that can only be constructed in specific places and/or ways. That "key" type can be moved around as needed to hand out "permission" to call such a closure "in the right place".

3 Likes

I was suspecting that's the case. It seems I was right about being wrong!

That reminds me of Javascript:

// immutable binding, mutable value
const a = [];
a.push("foobar");
console.log(a[0]) // "foobar"

Of course, JS is much more unsafe, despite not allowing const bindings to be redeclared as let (except by shadowing). I wish let could be redeclared as const (in JS, not Rust) though

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.