-
Notifications
You must be signed in to change notification settings - Fork 1.6k
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
RFC: const-dependent type system. #1657
Conversation
As currently written, this introduces parsing ambiguities:
|
From the type grammar I am not sure if floating point types (f34, f64) are allowed or not. Could you clarify this? |
@gnzlbg No. The [...] means "the rest of the scalar types". |
The section of the type grammar doesn't clearly indicate what has been added. |
I will update it.
Oh, that's right. I don't care about the proposed syntax, it was merely for demonstrational purposes. Any ideas on fixing these ambiguities/improving the syntax? |
I think the easiest way would be to require the |
We propose a simple, yet sufficiently expressive, addition of dependent-types | ||
(also known as, Π-types and value-types). | ||
|
||
Type checking remains decidable. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Isn't Rust's type checker already undecidable? Are you just suggesting that this proposal doesn't add any more undecidable language features? And if so, how do you know?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, yes and no. The recursion bound makes it decidable.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But, what I really mean is that there is no theorem proving involved (SMT or SAT solvers).
I believe earlier proposals required |
|
Given this code where fn x<n: const usize>() where a(n) {
y::<n>();
}
fn y<n: const usize>() where b(n) {} The compiler needs to be able to determine that As an example, consider this definition of const fn a(n: usize) -> bool {
n == 0 || n == 1 || n == 2
}
const fn b(n: usize) -> bool {
n == 1
} |
@Amanieu The point is to do the type checking lazily. fn x<n: const usize>() where a(n) {
y::<n>();
}
fn y<n: const usize>() where b(n) {} will compile, no matter what. But, when you start invoking it, the conditions are run. This is described in the document. |
So for example, fn x<n: const usize>() where a(n) {
y::<n>();
}
fn y<n: const usize>() where b(n) {}
const fn a(n: usize) -> bool {
n == 0 || n == 1 || n == 2
}
const fn b(n: usize) -> bool {
n == 1
} compiles, but calling the functions will fail, since x::<2>(); // checks a(2), suceeds.
// checks b(2), fails and throw error. Edit: To clarify, the error is thrown at compile time after the check failed. |
This is a big change from the way Rust currently deals with generics and should probably be listed as a drawback. I previously proposed adding compilation errors at monomorphization time for atomic operations (depending on platform support) in #1505, but people didn't seem to like the idea much. |
@Amanieu Well, often these are rejected for being "too narrow". This is a general solution, which allows for stuff like that, and a lot more. |
But yeah, it is a big change (as in semantics. In CLOC, probably not). |
Another thing to consider is passing it as arguments, which possibly allows for better syntax (i.e. |
fn<p: const usize, q: const usize, T, U> weird()
where T: Deref<Box<U>> + Copy,
T::Target: ?Sized,
Vec<U>: Borrow<T>,
p + q > 1 && p - q > 1 {}
foo::<1<2>()
fn f0<n: const usize>(p: [u8; n]) {}
fn f1<n: const usize>(p: [u8; n - 1]) {}
fn f2<n: const usize>(p: [u8; n + 100]) {}
fn f3<n: const usize>(p: [u8; n * 5]) {}
fn f4<n: const usize>(p: [u8; n*n*n*n*n - n*n*n*n - 40*n*n*n + 112*n*n - 25*n - 100]) {}
f0([0; 75]);
f1([0; 75]);
f2([0; 75]);
f3([0; 75]);
f4([0; 75]);
fn a<n: const Option<i32>>() {}
fn b<n: const (i32, u32, f32)>() {}
fn c<n: const &'static str>() {}
fn d<n: const Option<Mutex<Rc<fn(String)>>>() {}
a::<None>();
b::<(1, 3, 9.0)>();
c::<"hello">();
d::<None>();
fn foo<T, n: const T>() -> T { n }
fn bar<T, n: const Option<T>>() -> Option<T> { n }
fn baz<n: const usize, l: const [u32; n]>() -> [u32; n] { l }
let x = foo::<u16, 123>();
let y = bar::<Mutex<Rc<fn(String)>>>, None>();
let z = baz::<6, [1,2,3,4,5,6]>();
let z2 = baz::<_, [1,2,3,4,5,6]>(); // ? |
|
@ticki Correct me if I'm wrong, but the RFC text doesn't seem to talk about compile/run-time differences and it seems like these are compile-time constant value parameters that monomorphize like type parameters do. |
@eddyb, a depedent type is in the most literal sense, just a family of types paremeterized over a value, i.e. |
But yeah, it is CTFE-only. |
@ticki If all of 4 is impossible how would the compiler knows if a type implements a trait? impl<T, n: const usize> Clone for [T; n] { ... }
impl<T, n: const usize> InsaneClone for [T; n*n] { ... }
let p1 = [0; 64];
let q1 = p1.clone(); // <- we expect this line should compile, right?
let r1 = p1.insane_clone();
let p2 = [0; 72];
let q2 = p2.clone();
let r2 = p2.insane_clone(); // <- this line should not compile |
@ticki However, theoretical typesystems are simpler than the reality of an efficient/monomorphizing implementation. |
Well, you will have to specify the type. A general inference algorithm would require a SMT solver (which can be added if needed in the future). |
@eddyb Agreed. |
It seems that as is, any type that can be computed by If so, how do you deal with checking for equality? Structural or using a |
The lang team discussed this RFC a bit in our most recent meeting from a big-picture perspective. Here are some of our thoughts.
This plan, in particular, requires minimal bandwidth from the Rust teams (who are otherwise focused on the primary roadmap), but helps unblock other contributors from making progress here, while making sure there's at least rough agreement on the direction. What do you think? |
One extra point regarding the roadmap, I think this RFC would be particularly useful for people coming from C++. |
@aturon I agree with all the points you make, but I wish that cleaning up the constant evaluation in the Rust compiler will become a concrete objective for 2017, since it would:
Whether volunteers will fix associated consts, const fns, or decide to write a minimal type-level usize RFC, that is up to them and out of scope for the 2017 Roadmap. However, without a solid constant evaluation story, those RFCs are just unimplementable wishful thinking. Constant evaluation is an important enabler technology, we don't know everything that it will allow, and we really need the core team to make it solid. |
Reminder that @solson/miri is in active development and thanks to @solson and @oli-obk's dedication, it can handle all const-eval cases AFAIK and a large number of runtime tests (it turns out that emulating libc works well in practice). There's also going to be a compiler team dev sprint next year and we will at least try to come up with an acionable MVP for type-level constants, even if they might not be the highest priority. |
I would, in fact, want to see a lot more focus on MIR (borrowck & optimizations), as this year we ran out of bandwidth after pulling the switch, and most of the work done after that was on incremental recompilation. |
Thanks for laying out that argument! A couple of thoughts. First, while I don't disagree that improved const evaluation could impact some of the roadmap goals you mention, to some extent we need to think in terms of a global resource allocation problem.
So to the extent that const evaluation would take resources away from these other efforts, it'd be a net loss to our stated roadmap goals.
Understood, but again, there are a lot of potential enablers. Any of the various language integrations, for example, are potential enablers. The hard work of planning is that we have to make a guess as to the enablers likely to bear the greatest fruit. And that's exactly what the roadmap is trying to do. That said, this is not a zero-sum game: the Rust community is large and growing, and if we can unlock areas of work, we can potentially gain more total resources. That's part of why, despite not being on the roadmap per se, const eval is part of the upcoming compiler team sprint, as I mentioned above. The key goal there is to set out a basic plan that can unlock contributions to implementation. And as @eddyb notes, there are already people like @solson and @oli-obk laying down important foundations here. TL;DR: The Rust teams should devote most of their time to work that has the greatest impact on roadmap goals, but should also make small, targeted investments in planning and design sketches that unlock contributions elsewhere. |
@rfcbot fcp close Given the summary of where this RFC and the lang team stand, and the positive response to the steps described there, I'm going to propose FCP to close this RFC and request a fresh RFC taking the more minimalist approach mentioned in the summary. This does not entail rejecting the feature outright; rather, it's about getting to a more tightly-focused initial design. The lang team would be happy to work with stakeholders here to sketch out this more minimalistic design. A fresh RFC thread will help us focus in on the fine details there, and arrive at something plausible to be added after our general const story has been approved. |
Team member @aturon has proposed to close this. The next step is review by the rest of the tagged teams: No concerns currently listed. Once these reviewers reach consensus, this will enter its final comment period. If you spot a major issue that hasn't been raised at any point in this process, please speak up! See this document for info about what commands tagged team members can give me. |
I haven't seen the response, for some reason. Sorry I missed. Anyway, I will express that I am extremely disappointed for this close. Mainly because the reason doesn't seem clear at all. In fact, it seems more like a wildcard response to close without any real plan forward or fundamental concerns. This thread contains a lot of discussion and all criticism has been more or less fixed, it seems. For minimalistic design: It's kind of hard to define. There is a lot in this RFC, much of which is merely extension to the core proposal, but I can't see a way to more minimalistic describing a feature solving the problem. I can't say I approve of this decision without futher reason behind it, and perhaps a less vague response with actual plans. |
@ticki, just to check, did you read the summary of the lang team discussion? I feel like that comment was pretty detailed -- was there something specific there you felt was vague? In terms of all criticism being addressed, the linked comment mentioned the repeated lang team concerns about the scope of this RFC with respect to As I mentioned in the earlier comments, the lang team is more than happy to help guide an RFC here to completion, including having a dedicated meeting to sketch out the more minimal design, but given that we want to start with a much more pared down design, and the enormous length of this comment thread already, a fresh PR with revised RFC text seems best. Separately, it looks like RFC bot failed to mark this with the FCP label, which I'll add manually now. |
decidable) and performance, but the complications it adds to `rustc`. | ||
2. Monomorphisation-time errors, i.e. errors that happens during codegen of | ||
generic functions. We try to avoid adding _more_ of these (as noted by | ||
petrochenkov, these [already exists](https://github.com/rust-lang/rfcs/pull/1657#discussion_r68202733)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This link appears broken.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You need to expand some threads to find it. Here is the comment:
This error is currently reported during monomorphization, each of the arrays has acceptable size, but they cross the limit when combined together in one structure S:
struct S<T, U>(T, U); fn f<T, U>() { let s: S<T, U>; // error: the type `S<[u8; 70368744177664], [u8; 70368744177664]>` is too big } fn main() { f::<[u8; 1 << 46], [u8; 1 << 46]>(); }Overflows or division-by-zero in type level arithmetic with integer generic parameters feel very close to this situation, i.e. requiring where clauses for performing addition of two generic integers is similar to requiring where clauses for combining two generic types in a tuple like this:
struct S<T, U>(T, U) where sizeof(T) + sizeof(U) + PADDING < MAX_SIZE;
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wouldn't each of those arrays have terabytes of data? Who has this much ram?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@torpak: Consider mmap(2)
(which allows any file to be addressed as memory), and that there are indeed array-like datasets that large.
This is quite possibly one of the most beautiful and well-written RFC's I have ever read. I don't know how to thank the author enough, not only for their time and hard work, but also for working so hard to make the material accessible to those without a heavy background in PL and type theory. I know I myself learned so much simply by following this RFC these past months. I have some very mixed feelings about the decision to close this RFC. My background is as a C++ programmer who has been eyeing Rust's development steadily this past year. Firstly, I understand and empathize with the language team's concerns regarding the amount of work required to implement the technical aspects of this RFC as well as their concern about its potential to impact the 2017 Roadmap. That said, I feel that the author, @ticki, really showed commitment to this RFC by attempting to address some of the language team's specific concerns in his August 10th post in this thread. Also, I feel this feature impacts a number of the 2017 Roadmap goals, though perhaps not as obviously as it first might seem. One of the major goals I think that this feature implicitly impacts is the commitment that Rust's community should provide mentoring at all levels. I think to really scale this mentoring goal, one key element that I feel is important is that the Rust community grow. I feel that of all the various programming communities, C++ community members and Scala community members are particularly well positioned, and generally speaking, potentially most viable to be tapped to become Rust community members. Not only do these languages and Rust share methodologies and mindshare (RAII, a focus on concurrency, 'systems' programming, functional and generic programming, expressive type systems), they are all fairly complex languages. Yet I get the sense, and have seen evidence of, this general feeling from these communities that Rust isn't quite 'there' yet for various reasons. I think the addition of this feature and other type-system features would really bridge the gap in this perception and help persuade programmers and thought-leaders in those communities to engage more with Rust. I think the benefits in attracting members from these communities include not only gaining more mentors, but also include gaining more contributors to Rust and its ecosystem. I also think this feature also ties in directly and explicitly with the goals of ensuing the Rust language provides easy access to high quality crates, is well-equipped for writing robust, high-scale servers, and provides the foundation to build 1.0-level crates for essential tasks. Another item of note: I feel like the roadmap for 2017 is focused very hard on accessibility. One thing I am unsure of is to what kind of user the language is trying to be accessible. ESR's articles on Rust have gained a lot of attention in this community recently, and I am reminded of how frustrated C++ programmers feel trying to persuade C programmers to give their language a chance. Rust is, in my opinion, not positioned well to replace C. If the C++ community has a great difficulty selling C++, which is backwards compatible with C in many ways, to C thought-leaders, how much harder must it be to sell Rust to a C thought-leader? I think selling Rust to a C++ or a Scala thought-leader is potentially a much easier sell, if it didn't include those developers feeling like they might miss features that they often love the most:
While various members of the Rust community are working on all of these at the moment, I think there is a real perception from outside the Rust community, especially in the two aforementioned communities, that Rust isn't ready yet. I think the addition of this RFC could really generate a lot of interest in Rust, as this would be a very high-profile feature to thought-leaders in those communities. Just food for thought. |
Maybe it's a bit late to bring this up, but I was looking forward to seeing this RFC implemented, mostly for crypto-hashes project. (to the lesser extent the same goes for block-ciphers) I don't plan to publish 1.0 versions until type-level integers get stabilized, so in my case holding off this RFC contradicts one of the 2017 roadmap goals "Rust should have 1.0-level crates for essential tasks". I think the same story will be true for numeric and scientific crates. Currently I use Also I am looking forward to be able to write conversion functions which will be able to safely and generically convert e.g. |
@fottey I want this feature too. But because I want to use it. Not because it helps people learning Rust by giving them every feature from their current language. Going down that path is fraught with kitchen sinks and OO misfeatures. Thought leaders will come to Rust when they see awesome software being released. |
@fottey Thanks for sharing your thoughts. I want to re-emphasize what I've said in the last comments: moving to close this RFC is not about rejecting this feature altogether, but about restarting discussion around a kernel proposal. In the response from @ticki that you mention, they proposed to split the RFC into separate ones, one covering the simplest mechanism needed to make const generics work, and the other layering on new |
@fottey thanks a lot for the kind words. It has gone through a lot of iteration, and quite frankly, it was a train-wreck before feedback. This is a powerful strength of a community. @aturon Ok, I still disagree that it is better off split up (I believe I explained why earlier in the discussion), but I'll accept it as a plan and will create new RFCs when I get time. |
Thanks @ticki! Feel free to reach out to the lang team if you'd like to talk about the initial, minimal feature before opening a new RFC -- we'd be glad to set up a meeting or give feedback on a draft. And if you're busy, it's plausible we can find some people to help make a new draft. I'd also suggest opening just that core RFC to start with -- we should reach agreement on the core of the feature, which will take some time to implement in any case, before starting to explore significant expansions. I'm going to go ahead and close this RFC, in anticipation of a new, more minimal proposal. Thanks all for the great discussion so far! |
I want to add, that this, at least in a simple form, which allows compile time variable sized arrays seems to be really important to the rust ecosystem, as exemplified by the reverse dependencies of the hack people have to currently use: https://crates.io/crates/generic-array/reverse_dependencies |
|
@ticki I'm pretty sure @iqualfragile was only commenting on the usefulness of such a feature, not the design of that specific library.
|
Introduce Π type constructors for Rust.
Rendered