Pre RFC: Function properties
A function property is a thing that functions can have if any function they calls have that property, plus additional restrictions specific to each property.
There can be several function properties (The exact set of properties doesn't really matter):
- no_panic
- no_oom
- no_alloc
- no_recursion (can also be used to enforce a maximum size of stack at compile time)
- no_unbound_loop
- const (the current const fns, constness of a function is a function property)
Function properties are annotated via special @prop
syntax:
@no_oom
// @no_panic is not valid, since x + 2 may overflow
@const // or simply const, since const is a keyword
fn foo(x: i32) -> i32 {
x + 2
}
Function properties can be considered as inverse of an effect system. For example, if we consider
panic an effect, no_panic
means lack of panic effect.
Function properties enables compile time checking of them, and guarantee them in library
apis. And it can enable analysis for other language features, for example if we annotate
sizeof<T>
with no_panic
, no_recursion
, no_unbound_loop
and const
, we can use
it in
struct Foo<T> { bytes: [u8; sizeof::<T>()] }
without wellfoundness concerns.
Too many annotations everywhere
In order to reduce the burden of annotations, some shortcuts can help:
- no_fail: no_panic + no_oom
- terminates: no_recursion + no_unbound_loop
- total: terminates + no_panic
In addition to that, properties can defined on modules or inherent impls, and they will be applied to each function.
Trait functions and generics
We can apply a function property on a trait impl, and all of functions will get that property (like an
inherent impl). In generic bounds, @prop Trait
means that impl of the trait needs to have
that property. So we can write this function:
@no_panic
@no_oom
fn foo<T: @no_panic @no_oom Trait>(t: T) {
t.trait_func();
}
But it will restricts the callers of foo
. We want to allow impl of T: Trait
to panic if caller of
foo doesn't needs no_panic
. Generic properties solve this problem:
@P ~ { no_panic, no_oom } // P is a subset of { no_panic, no_oom }
fn foo<T: @P Trait>(t: T) {
t.trait_func();
}
This means foo has property P
which is a subset of no_panic + no_oom
, depends on implementation of
the Trait
. There is currently an unstable ~const
that solve this problem for const fns in a
different way. ~const Trait
can be non-const in normal context and should be const in const context. A
function is const regardless of what it is called with. But with generic properties, a function is const
or no_panic if the trait satisfy const or no_panic. These are just different points of view and in
action they are equal, like:
const fn triple_add<T: ~const Add<Output=T>>(a: T, b: T, c: T) -> T {
a + b + c
}
is equivalent to:
@P ~ { const } // or P ~ { const, total, no_fail }
fn triple_add<T: @P Add<Output=T>>(a: T, b: T, c: T) -> T {
a + b + c
}
We can add ~prop trait
as a sugar, or even use it instead of that generic property syntax
described above. But personally I found above syntax easier to understand.