Continuing the discussion from Paying developers for pull requests:
As it turns out, I've been thinking about how to do this for a while now, chewing over different ideas. Some possible directions:
- Develop a software verification language, analogous to hardware verification languages in chip design.
- Strongly encourage the use of types everywhere, so that we can take advantage of the Curry-Howard correspondence and type theory in general.
- Make
assert!()
(and related macros) into full keywords in the language.
The first point would mean that one needs to learn rust, then maybe how proc macros work, and finally learn the verification language. That is an incredible amount of work, and will likely lead to the weirdness that was Objective-C++.
The second point leads to exponential growth in the number of types, even when you use traits and trait bounds to the maximum you possibly can. E.g., an integer can be valid over some range, if it is interpreted as kilograms, etc., etc. etc. That isn't really sustainable in the long run.
The third point is the interesting one. Suppose that assert!()
were a keyword in rust. You could express conditions like the following:
fn foo(i: isize) -> u8 {
assert!(i < 256);
assert!(i >= 0);
i as u8
}
fn big(i: isize) -> isize {
assert!(i >= 256);
i
}
pub fn bar(i: isize) -> u8 {
foo(i)
}
pub fn baz() -> u8 {
foo(23)
}
pub fn broken(i: isize) -> isize {
let temp: u8 = foo(i);
let temp: isize = u8 as isize;
big(temp)
}
The compiler would have the ability to do all of the following:
-
If some pieces of information are unknown, then the current behavior for
assert!()
would continue to be used, generating code that is executed at runtime. So the compiled version ofbar()
might havefoo()
inlined into it,assert!()
statements and all. -
If all pieces of information are known at compile time, then something like Chalk could verify that the claims are true statically. Thus, for
baz()
the internals could be simplified to23
. -
Verification code could be pushed out to the public interface. That is, the
assert!()
code infoo()
need not actually be in that block; we know thatfoo()
is a private function, and therefore not visible outside of the crate. We could move the test into the public functionsbar()
andbaz()
instead. Sincebaz()
can be simplified to23
, we could remove the verification code entirely. -
Verification statements could be combined to see if any inputs can satisfy the constraints.
broken()
cannot be satisfied under any conditions, and this can be verified at compile time. -
Testing and verification can be reduced significantly. Right now, the only way I can verify that
baz()
works correctly (or thatbroken()
is indeed broken) is by creating a unit test for it and seeing if any assertions are triggered. For functions that have complex arguments, or for methods of objects with a large amount of state, this can be very difficult to test; either the programmer needs to know all of the triggering conditions, or has to use a good fuzzer to try to find combinations of inputs that trigger failures. Building in a proof checker like this means that you don't have to worry about that. -
It would make the type system itself more powerful, and would make optimizations possible that aren't possible today. For example, consider the following code:
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] struct Foo { field: f64 } impl Foo { pub new(field: f64) -> Foo { assert!(!field.is_nan()); Foo{field} } }
in the far future, Chalk might be able to determine statically that the only way to set
Foo::field
is viaFoo::new()
, and that means thatfield
can never beNaN
. That means that the derive ofEq
, andOrd
would be valid. I don't mean to imply that this will be easy to implement, just that it would be possible.
Finally, making assert!()
a keyword is the least intrusive method I can think of for the end user of rust. You don't need to learn a new language, you don't have to add in other code separate from your codebase that you need to maintain, and you don't have combinatorial explosion from an excess of types.
So, thoughts?
FINAL OUTCOME OF DISCUSSIONS
This is what I consider to be a final outcome of the discussions. THE PROPOSAL IS REJECTED.
After thorough discussions, two things were finally noted. First, because assert!()
is a part of libcore
and libstd
, attempts to redefine it would lead to compile errors due to the ambiguity as to which version of assert!()
was in use. That ensures that misguided (or malicious) attempts at replacing assert!()
will be detected at compile time, ensuring that there is only one 'real' definition of assert!()
already.
Second, the macros in libcore/libstd
are in @CAD97's words, special. For at least some of the macros, the compiler is already aware of them, which is why they work. assert!()
can be treated in exactly the same manner already.
Combining these facts together, assert!()
is already close enough to being a keyword that formalizing it as such is unnecessary, which is why the proposal was rejected.