I suggest a new tag, #[invariant] that can be put on types to assert any invariant called for by the type.
#[invariant(condition)] // This condition will be `assert`ed every time a function taking mutable references of the struct is called.
struct InvariantType;
This will provide users with strict enforcement of invariants to ensure that at any time a function is called, the invariant will be upheld.
Something similar to:
invarianted_variable.modify();
// assert(invariant) automatically called
Perhaps this could be made const?
I'd love to hear feedback!
D has a similar feature where a type can have an invariant {} block in its definition. Every method then runs through the block to ensure it is valid. Not sure if it depends on configuration or not.
Anyways, RFC-like things really benefit from the RFC template so everyone has a better idea of what is actually being proposed. For example, what is the inv name and where else does it appear? Is it user-controlled? A method? Can it be private? And so on.
Sorry, I didn't make it clear that this is a very nebulous idea. Also, the inv is just a generic name for a condition. Edited my post to make it clear.
Having a look at the documentation, it seems that this is very clear prior art. This could theoretically be restructured, but I believe for our purposes the #[invariant] tag is better syntax due to how Rust is structured.
Additionally bringing to note that Ada and Eiffel have similar features of invariants.
contracts - Rust also brings in similar features to the table.
This is also one of Rust's project goals.
It seems like it would be possible to implement this as a proc-macro on impl blocks. The biggest downside to that approach being duplication on trait impls and not being able to derive.
I was thinking more like putting it in struct, enum, and typedef definitions.
That way, we can have types with additional invariants be defined as typedefs and we would put the proc macro right at the source.
There is a crate that does this but with rust's project goals, I suggest implementing it natively as a better option.
I would prefer it be implemented like D - with the invariants being checked at the constructor and every function calling a mutable reference to the type.
In D, at least, the invariant block is automatically called at the end of every method (could probably be limited to methods which accept &mut, but interior mutability also exists…). As I said above, that may depend on build configuration; I don't think I've ever done non-debug builds in D (while developing).
So meant for ensuring that your implementation upholds and invariant you have decided? I'd think its very uncommon for a type to be complicated enough that your implementation would benefit from automatic invariant checks, while also having an invariant that can be checked trivialy. (I at least cannot come up with an example that would benefit enough from this)
Is there a reason this would need to be a language builtin/in the stdlib? The contract crate seems just fine to me. I don't think it lets you annotate a type and have it checked everywhere, but you could define an is_valid method and ensures it everywhere you need. In fact, that method seems better, since:
It allows internal methods to operate on values that don't uphold the invariant
It allows the same level of validation on free functions as methods
It avoids checking and rechecking the invariants of the same data (.len() probably doesn't need to check every time that it's greater than .cap())
Note that type does not really define a new type (to which you can attach invariants) but instead it's simply an alias for an existing one. Making this work as you expect would likely require reworking some existing assumptions.
I think a way to specify custom invariants on a type would be most useful if it's assert_unchecked whenever using the type (combined with automatically inserted asserts that ensure that the inserted assert_uncheckeds can't possilby fail – unfortunately I'm not clear on the details of how this would work, especially with respect to how it interacts with unsafe code).
The advantage of this is that, in addition to catching coding mistakes in which the invariant is broken, it also improves performance because the compiler can assume the invariants rather than rechecking them every time.
Because to me when I see it, I think of the contracts work where you run an SMT solver to prove that the invariant holds, not that you'd repeatedly assert it all over the place.
(Especially since, to me, the most important invariants are the ones you can'tassert.)
How would this be implemented? It seems totally infeasible, because every reference to anything (e.g. &mut u8) would need to carry with it at runtime some information about invariants to verify on every access.
Would it need to be runtime information? Didn't we manage to get rid of the explicit drop flag logic with smarter compilation? Why not do the same with inserting calls to the invariant verifier? Usage of &T where T: Freeze could also elide such calls.