Hi,
i am no sure if I understand what you mean, so correct me if my assumptions are wrong, but:
I believe my approach works also when you use multiple functions, for example you can do:
fn inner_helper<F: ?Sized + CapB>(format: &mut F) {
format.do_b(2);
}
fn outer_helper<F: ?Sized + CapB>(format: &mut F) {
inner_helper(format);
}
algorithm!{format:F:CapB: complex_algorithm(value: i32) {
outer_helper(format);
}}
fn main() {
let mut format: Box<dyn Format> = Box::new(SomeFormat);
dbg!(complex_algorithm(&mut *format, 7));
}
Now if you, for example, change body of inner_helper
to:
format.do_a();
the compiler will raise an error. One way to fix that would be to update bounds:
fn inner_helper<F: ?Sized + CapA>(format: &mut F) {
// ^^^^
format.do_a();
}
now the compiler will raise an error for outer_helper
, so you have to update its bounds too:
fn outer_helper<F: ?Sized + CapA>(format: &mut F) {
// ^^^^
inner_helper(format);
}
which will raise another error and force you to update bounds on complex_algorithm
:
algorithm!{format:F:CapA: complex_algorithm(value: i32) {
// ^^^^
outer_helper(format);
}}
and now the program compiles again and consistency of static and dynamic checks is restored.
So as I said before, this does not scan function for uses of capabilities, but instead ensures the static and dynamic checks are consistent (by generating them both in single step) and leaves the rest of the work (the scanning part) to the compiler.
I am not sure if this approach is suitable for your use case but it seems a reasonable alternative to me.
Also I have found some flaws in my original approach, so if it is any useful:
- There is nothing to prevent you to call generic function (like e.g.
inner_helper
) directly bypassing checks
- I have found a nicer macro to do the checks.
To solve #1 we can use a wrapper newtype like:
struct FormatWrapper(Box<dyn Format>);
impl FormatWrapper {
/// Do not use this directly,
/// use proper macro to obtain a reference to the wrapped format.
fn get_inner(&mut self) -> &mut dyn Format {
&mut *self.0
}
}
struct SomeFormat(());
impl SomeFormat {
// Make sure that formats are never accessible in their unwrapped form without capability checks
fn new() -> FormatWrapper {
FormatWrapper(Box::new(Self(())))
}
}
Now you cannot call the helper function directly. Instead you need to use a macro to extract the inner &mut dyn Format
that would also do the necessary checks:
macro_rules! unwrap_check_format{
($format:expr; $($cap:ident),*) => {{
fn extract(wrapper: &mut FormatWrapper) -> Option<&mut (impl ?Sized $(+ $cap)*)> {
let format = wrapper.get_inner();
$(if !format.has_cap(Cap::$cap) { return None; })*
Some(format)
}
extract($format)
}}
}
fn other_algorithm(format: &mut FormatWrapper, value: i32) -> Option<()> {
let format = unwrap_check_format!(format; CapA)?;
Some(outer_helper(format))
}
fn main() {
let mut format = SomeFormat::new();
dbg!(other_algorithm(&mut format, 7));
}
(Playground with my experiments on this topic)