-
Notifications
You must be signed in to change notification settings - Fork 17
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
When exactly is a constant well formed? #49
Comments
Nit: fn baz<T>() -> [u8; unsafe { std::mem::transmute<u8, bool>(size_of::<T>()) }]
// not well formed, for any `T` with `size_of::<T>() > 3` the final value is not a valid bool. needs to be fn baz<T>() -> [u8; unsafe { std::mem::transmute<u8, bool>(size_of::<T>() as u8) as usize }]
// not well formed, for any `T` with `size_of::<T>() > 3` the final value is not a valid bool. We currently do not detect this case because of performance reasons. It is trivial (a single flag) to turn on that locals are checked for validty after each modifiction |
We detect it in the final value of a constant afaict. So
|
Yes, but we don't detect it in intermediate computations like when it's stored in a local variable but never returned. |
|
The problem with this definition is that it is uncheckable (except with major effort). I thought the point of well-formedness is for the compiler to check it? Similar to how it checks that e.g. lifetimes appropriately outlive each other? If that is the case, we need a notion of well-formedness that is efficiently decidable -- which the one I quoted above is not. |
The way that it is proposed to check this is very cheap: If it is still generic, you need a 1:1 copy of the constant in a |
So basically this system punts the question of polymorphic constants? We don't even define what well-formedness means for them, but we ensure that the context always assumes well-formedness? I'm somewhat surprised this works, and it also seems very restrictive, but -- yeah, sounds plausible. I'd change the text then and not say things like "polymorphic constants are well-formed if all instances are well-formed". Such a statement says that e.g. this is well-formed: fn bar<T>() -> [u8; 64 + align_of::<T>() * 0] {} Or this: fn bar<T>() -> [u8; 64 / std::min(size_of::<T>(), 1)] {} But it's not well-formed, due to a lack of |
I don't see why "The problem with this definition is that it is uncheckable" is a problem in practice. Just like the borrow checker prevents programs which are theoretically correct, so too can our const well formed check forbid theoretically correct problems. So at least with how I understand this, both So we could theoretically add a potentially unsafe function |
yes
yes
it is very restrictive, but it's not actually worse than the status quo ( |
But then we need a different name for "the thing the compiler checks". So far, "well-formedness" was the thing that the compiler checked, not a more relaxed thing that we could theoretically also allow. This RFC does a good job of describing that thing (though it may be outdated). In the syntax of that RFC, there would simply be no rule that says when polymorphic consts are well-formed, which implicitly makes forwarding the requirement the only way. If you suddenly use the term "well-formed" for something that is not what the compiler actually checks, that is going to be very confusing. Please don't change the meaning of established terms like that. I thus propose that we say something like:
Regarding the monomorphic case, shouldn't it also require that the constant must be convertible to a value tree? |
I doubt we can do that, actually. Much of the compiler relies on well-formedness as an invariant, as far as I know. Such a method would likely trigger ICEs all over the place. I don't think the theoretical possibility of re-architecting everything to allow such a method justifies what I consider to be a change in the meaning of the pre-existing term "well-formedness". |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
Summary: we can change what "well formed constant" means, but when talking about well formed constants, it must be whatever the compiler currently accepts as well formed. We don't talk about "stable" things when talking about things that "could be stable", either. Please let's get back to discussing what it means to have a "well formed constant" and explicitly mention when something is "we could change well formedness rules to accept this in the future". |
Ok, so from what I can tell, a given crate/type/whatever is well formed iff the language accepts it. IOW both things which are not well formed but are currently accepted and things which are well formed and not accepted are bugs of the compiler. Is that correct? At this point I am not actually thinking about "well formedness" here and need a better word for this 🤔 So the "correct" definition for well formed contants would be: Well formed constants are currently either concrete constants (with the above properties) or const params. And after rust-lang/compiler-team#340 is implemented, constants are also well formed if they are mentioned in the What would |
Not well formed, unless |
Regarding the terminology, this is very similar to things like "well-typed". A program is "well-typed" if it gets accepted by the compiler. The purpose of writing down a type system precisely and formally is to have a representation of that that's easier to read than 10k lines of code doing type-checking. The following code is not well-typed, even though it could be (because nothing can ever go wrong when executing that function): fn foo() -> i32 {
if 0 == 1 { false } else { 13 }
} So just like the above function is not well-typed even though a smarter compiler could accept it, we also consider types not well-formed based on what we actually can reasonably check and want to implement, not on what is theoretically possible. For "well-typed", we say that functions like
Agreed.
But before we go down that rabbit hole, we should have some idea for where we are going. This is moving towards arbitrary powerful static analysis to determine if CTFE will succeed with every possible input. We could throw an SMT solver at this but we probably do not want to. I think it is better to accept no such code than to have a piecemeal solution that accepts some random things but not others, in an unpredictable way. That's for way in the future anyways. I hope.^^ |
So my current understanding is that all constants used in types must be well formed.
I wasn't able to find any discussion on what exactly that means though 😅
I think that for concrete constants the following must hold:
Constants evaluate successfully
This means that all of
1usize - 2
,panic!()
,loop {}
and[1, 2, 3][3]
are not well formed as constants.An evaluated constant must satisfy all lang invariants of its type
So something like
unsafe { std::mem::transmute::<u8, bool>(2) }
is not wf.I do think that the following would be well formed though, as it only breaks library invariants:
The more interesting case are polymorphic constants as we can't just evaluate them.
For these I think the following rule is appropriate
A polymorphic constant is well formed, iff all possible concrete instances are well formed
Some examples:
Does this definition make sense?
The text was updated successfully, but these errors were encountered: