Implement const generic bounds in order to check if they are well formed

Hello everyone, I have some free time and thought I could contribute a bit to the rust compiler. I have a bit of experience implementing stuff and already know what I want to try (proposal 2 from this comment I know that it might be a bit early to start implementing such a vague proposal, but it sparked my interest and I mostly am doing it in order to get a bit familiar with the internals of rustc (so please forgive me if there are some obvious missuses of types).

TL;DR of the proposal: The success of compiling the following code depends on the concrete value of C, resulting in post-monomorphisation errors, which are undesired.

fn foo<const C: usize>() {
    let x = [u8; C - 2];

Proposal: Add a type of where bound that exposes all const-generic expressions inside the function so that the call site can check them pre-monomorphisation:

fn foo<const C: usize>() where (C-2) {
    let x = [u8; C - 2];

Obviously all just hypothetical syntax.

I'm working based on this rustc commit: 0f5c76951327b912c8e92e83235430ebd9b349d9

What I have done already:

  • added some required variants and types to rustc_ast in order parse the syntax (WherePredicate::ConstPredicate basically a wrapper around AnonConst)
  • added the corresponding types to rustc_ast_lowering
  • added and stubbed the doc types
  • The following code gets parsed into an AST (contrary to the example above I used ={} as the syntax, as it was easier to implement and could obviously be changed later on. (For the interested, the problem with where (<const expr>) was that their exist types that start with an ( and as such parsing it would require more logic.
fn foo<const C: usize>() where ={C-2} {
    let x = [u8; C - 2];

Where I'm currently unsure if it is the correct approach is the following (in rustc_typecheck/src/collectstarting at line 2042 ):

&hir::WherePredicate::ConstPredicate(ref const_pred) => {
    let const_def_id = icx.tcx.hir().local_def_id(const_pred.const_expr.hir_id);
    let c = ty::Const::from_anon_const(icx.tcx, const_def_id);
    let pred = ty::PredicateAtom::WellFormed(c.into());
    predicates.push((pred.to_predicate(icx.tcx), const_pred.span));

What happens here is that I take the anonymous const expression from the where bound and put it inside a WellFormed predicate.

Now I currently have two questions:

Is it correct to use WellFormed predicate? There is also a ConstEvaluatable predicate, however I could not find out how to correctly set the SubstRef field.

Currently the code fails to compile with an error in rustc_typecheck/src/collect/type_of where it tries to find out what type the AnonConst is. This happens due to the code encountering an unexpected parent node variant (in this case a Fn). My guess would be that I should be able to treat it as if the expression was found inside the function body and was wondering if the lines 312-316 are the ones responsible for this case? (I don't really understand line 304-310 as they just return the type usize, maybe someone could explain to me what case this is?).

I already have a rough idea what the next step should be after this:

  • Check for every const expr inside a function (including where bound on called functions) that it can be evaluated pre-monomorph or if not, that it exists as a where bound

In case this is the wrong place for these questions I would appreciate a hint for the correct place :slight_smile:

Thanks in advance, Raidwas

1 Like

Hi @Raidwas, it's good to see someone investigating this issue in more depth! I would suggest asking on the #t-compiler channel on Zulip, which is a good place to discuss questions about the compiler like this one. If you start a new thread, and post a link to your branch, it'll also be easier to take a look and give suggestions.

It's worth leaving a comment on to mention that you're working on this, in case anyone has some comments there (some people won't be keeping up with IRLO).

I think solution for this problem should go via RFC first (or the lang team should make an official desicion on the matter). As argued in the linked thread, personally I think that adding such explicit bounds will be a mistake and I strongly prefer the third option (compiler will automatically infer and track such bounds implicitly), but which would work globally, not limited to a function body.

Note, that I do want to be able to add bounds like "C should be in this range" or "C should be element of the given set", but I do not want the mandatory "dumb" bounds.

I did not say any of the solution should be preferred over another (while I have a preference). Currently I'm just doing this in order to understand a bit how rustc works.

And yes, I agree with you that anything in this direction would first have to go trough some RFC process :slight_smile:

Just a note that there's already an extensive Zulip thread about the implementation of proposal 2, which would be worth checking out and commenting on. (I had forgotten about this previously, apologies.)