Skip to content
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

Contract cannot be applied to const functions #136925

Open
tautschnig opened this issue Feb 12, 2025 · 16 comments · May be fixed by #138374
Open

Contract cannot be applied to const functions #136925

tautschnig opened this issue Feb 12, 2025 · 16 comments · May be fixed by #138374
Assignees
Labels
C-bug Category: This is a bug. F-contracts `#![feature(contracts)]` requires-incomplete-features This issue requires the use of incomplete features. S-has-mcve Status: A Minimal Complete and Verifiable Example has been found for this issue T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.

Comments

@tautschnig
Copy link

tautschnig commented Feb 12, 2025

I tried this code (minimal reproducer extracted from #136578):

#![feature(contracts)]
//~^ WARN the feature `contracts` is incomplete and may not be safe to use and/or cause compiler crashes [incomplete_features]

#[core::contracts::ensures(move |ret: &u32| *ret > x)]
const fn broken_sum(x: u32, y: u32) -> u32 {
    x + y
}

fn main() {
    assert_eq!(broken_sum(0, 1), 1);
}

I expected to see this happen: compilation would succeed

Instead, this happened: compiler rejected the code with the following errors:

error[E0015]: cannot call non-const function `build_check_ensures::<u32, {closure@contracts-const.rs:4:28: 4:44}>` in constant functions
 --> contracts-const.rs:4:1
  |
4 | #[core::contracts::ensures(move |ret: &u32| *ret > x)]
  | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |
  = note: calls in constant functions are limited to constant functions, tuple structs and tuple variants

error[E0015]: cannot call non-const closure in constant functions
 --> contracts-const.rs:4:1
  |
4 | #[core::contracts::ensures(move |ret: &u32| *ret > x)]
  | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |
  = note: calls in constant functions are limited to constant functions, tuple structs and tuple variants

error: aborting due to 2 previous errors; 1 warning emitted

For more information about this error, try `rustc --explain E0015`.

Meta

rustc --version --verbose:

$ build/x86_64-unknown-linux-gnu/stage2/bin/rustc --version --verbose
rustc 1.86.0-nightly (69482e8e5 2025-02-11)
binary: rustc
commit-hash: 69482e8e5a5fa1441615b015ac2f59178d944696
commit-date: 2025-02-11
host: x86_64-unknown-linux-gnu
release: 1.86.0-nightly
LLVM version: 19.1.7
@tautschnig tautschnig added the C-bug Category: This is a bug. label Feb 12, 2025
@rustbot rustbot added the needs-triage This issue may need triage. Remove it if it has been sufficiently triaged. label Feb 12, 2025
@tautschnig
Copy link
Author

@rustbot labels +F-contracts

@rustbot rustbot added the F-contracts `#![feature(contracts)]` label Feb 12, 2025
@jieyouxu jieyouxu added T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. requires-incomplete-features This issue requires the use of incomplete features. S-has-mcve Status: A Minimal Complete and Verifiable Example has been found for this issue and removed needs-triage This issue may need triage. Remove it if it has been sufficiently triaged. labels Feb 12, 2025
@celinval
Copy link
Contributor

@rustbot claim

@compiler-errors
Copy link
Member

I don't expect this to be fixable anytime soon, since it relies on const closures which are not even coherently designed yet.

@celinval
Copy link
Contributor

This might require some design changes

@celinval
Copy link
Contributor

One possible workaround would be to remove the default body of the contract intrinsics, and implement them in the backend. In that case, contracts would not be checkable during constant evaluation. I'll see if I can think about other ways to fix this.

@celinval
Copy link
Contributor

I think I found a better work around. We could use const_eval_select!{} macro to only evaluate the contract at runtime which will allow us to annotate constant functions. The downsides would be the following:

  1. The contract will be ignored during constant evaluation.
  2. This will allow users to use non-constant functions as part of constant functions contract. I'm not sure whether we want this or not though.

So we should track these limitations and solve them before stabilization.

@tautschnig @compiler-errors does this solution sound reasonable for now?

@compiler-errors
Copy link
Member

So, const_eval_select only works with free functions today:

https://play.rust-lang.org/?version=nightly&mode=debug&edition=2024&gist=7ce4c7d9a17714d72794f35d398e6d29

You can't pass closures to them until that functionality is specifically implemented in the compiler, which is blocked on constifying the Fn traits in the standard library, which is blocked on ~const trait bounds in general which is likely going to take a few months to resolve at best.

@compiler-errors
Copy link
Member

And I don't think contracts can use the const_eval_select!, 1. because lowering contracts happens in ast resolution, which is post-macro-expansion, and 2. it requires a lot of type information for the lowering since it's really just a macro to expand into function items, and we simply don't have sufficient type information during macro resolution except for contract predicates that literally just don't do anything interesting.

@compiler-errors
Copy link
Member

At least that's my understanding. You're welcome to try, though, but I'd rather not commit to hacks here, and perhaps go back to the drawing board.

@celinval
Copy link
Contributor

The idea would be to apply the const_eval_select in the body of the intrinsic. Something like this:

pub const fn contract_check_requires<C: Fn() -> bool + Copy>(cond: C) {
    const_eval_select!(
        @capture[C: Fn() -> bool + Copy] { cond: C } :
        if const {
                // Do nothing
        } else {
            if contract_checks() && !cond() {
                // Emit no unwind panic in case this was a safety requirement.
                crate::panicking::panic_nounwind("failed requires check");
            }
        }
    )
 }

The AST lowering injects a call to the intrinsic.

@compiler-errors
Copy link
Member

Well the problem is that build_check_ensures returns a closure is not callable in a const context (b/c const closures do not exist today).

@celinval
Copy link
Contributor

Sure... but I believe we can change that. Let me get something working, and see how that looks.

BTW, I'm not opposed to rethinking the implementation. I think we can solve this issue now with const_eval_select as we think on a better long term approach.

@RalfJung
Copy link
Member

RalfJung commented Mar 19, 2025

I don't see how that approach (via const_eval_select!) should work with the lowering around build_check_ensures. IIUC, ensures translates into something like

fn foo() { 
  let _check = build_check_ensures(PRED);
   ... [return _check(R);] ...
}

That means the transformed code makes fundamental use of closures, which are fundamentally not supported in const fn.

@celinval
Copy link
Contributor

celinval commented Mar 19, 2025

@RalfJung, you are correct that the existing logic wouldn't work. If you take a look at #138374, I had to remove the extra closure added by build_check_ensures. In that PR, the closures are only used to wrap the contracts predicate and they are only invoked in the contract intrinsics.

@celinval celinval linked a pull request Mar 19, 2025 that will close this issue
@RalfJung
Copy link
Member

Ah there's a PR for it already, interesting. It wasn't referenced anywhere in this issue so there's no way I could have known this.^^

@celinval
Copy link
Contributor

My bad, I just linked them a few minutes ago

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
C-bug Category: This is a bug. F-contracts `#![feature(contracts)]` requires-incomplete-features This issue requires the use of incomplete features. S-has-mcve Status: A Minimal Complete and Verifiable Example has been found for this issue T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

6 participants