Skip to content

Conversation

dawidl022
Copy link
Contributor

@dawidl022 dawidl022 commented Jul 25, 2025

This change adds contract variables that can be declared in the requires clause and can be referenced both in requires and ensures, subject to usual borrow checking rules. This allows any setup common to both the requires and ensures clauses to only be done once.

In particular, one future use case would be for Fulminate-like ownership assertions in contracts, that are essentially side-effects, and executing them twice would alter the semantics of the contract.

As of this change, requires can now be an arbitrary sequence of statements, with the final expression being of type bool. They are executed in sequence as expected, before checking if the final bool expression holds.

This PR depends on #144438 (which has now been merged).

Contracts tracking issue: #128044

Other changes introduced:

  • Contract macros now wrap the content in braces to produce blocks, meaning there's no need to wrap the content in {} when using multiple statements. The change is backwards compatible, in that wrapping the content in {} still works as before. The macros also now treat requires and ensures uniformally, meaning the requires closure is built inside the parser, as opposed to in the macro.

Known limiatations:

  • Contracts with variable declarations are subject to the regular borrow checking rules, and the way contracts are currently lowered limits the usefulness of contract variable declarations. Consider the below example:

    #[requires(let init_x = *x; true)]
    #[ensures(move |_| *x == 2 * init_x)]
    fn double_in_place(x: &mut i32) {
        *x *= 2;
    }

    We have used the new variable declarations feature to remember the initial value pointed to by x, however, moving x into the ensures does not pass the borrow checker, meaning the above function contract is illegal. Ideally, something like the above should be expressable in contracts.

@rustbot rustbot added S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. T-libs Relevant to the library team, which will review and decide on the PR/issue. labels Jul 25, 2025
@dawidl022 dawidl022 force-pushed the contracts/variable-scoping-rebased branch from 68621b6 to 4a765bc Compare July 25, 2025 11:03
@rust-log-analyzer

This comment has been minimized.

@rust-log-analyzer

This comment has been minimized.

@dawidl022 dawidl022 force-pushed the contracts/variable-scoping-rebased branch from ab96f97 to e41951a Compare July 25, 2025 11:30
@rust-log-analyzer

This comment has been minimized.

matthiaskrgr added a commit to matthiaskrgr/rust that referenced this pull request Oct 16, 2025
…ng, r=oli-obk

Guard HIR lowered contracts with `contract_checks`

Refactor contract HIR lowering to ensure no contract code is executed when contract-checks are disabled.

The call to `contract_checks` is moved to inside the lowered fn body, and contract closures are built conditionally, ensuring no side-effects present in contracts occur when those are disabled. This partially addresses rust-lang#139548, i.e. the bad behavior no longer happens with contract checks disabled (`-Zcontract-checks=no`).

The change is made in preparation for adding contract variable declarations - variables declared before the `requires` assertion, and accessible from both `requires` and `ensures`, but not in the function body (PR rust-lang#144444). As those declarations may also have side-effects, it's good to guard them with `contract_checks` - the new lowering approach allows for this to be done easily.

Contracts tracking issue: rust-lang#128044

**Known limiatations**:

- It is still possible to early return from the *function* from within a contract, e.g.

  ```rust
  #[ensures({if x > 0 { return 0 }; |_| true})]
  fn foo(x: u32) -> i32 {
      42
  }
  ```

  When `foo` is called with an argument greater than 0, instead of `42`, `0` will be returned.

  As this is not a regression, it is not addressed in this PR. However, it may be worth revisiting later down the line, as users may expect a form of early return from *contract specifications*, and so returning from the entire *function* could cause confusion.

- ~Contracts are still not optimised out when disabled. Currently, even when contracts are disabled, the code generated causes existing optimisations to fail, meaning even disabled contracts could impact runtime performance. This issue is blocking rust-lang#136578, and has not been addressed in this PR, i.e. the `mir-opt` and `codegen` tests that fail in rust-lang#136578 still fail with these new HIR lowering changes.~ Contracts should now be optimised out when disabled, however some regressions tests still need to be added to be sure that is indeed the case.
rust-timer added a commit that referenced this pull request Oct 16, 2025
Rollup merge of #144438 - dawidl022:contracts/guarded-lowering, r=oli-obk

Guard HIR lowered contracts with `contract_checks`

Refactor contract HIR lowering to ensure no contract code is executed when contract-checks are disabled.

The call to `contract_checks` is moved to inside the lowered fn body, and contract closures are built conditionally, ensuring no side-effects present in contracts occur when those are disabled. This partially addresses #139548, i.e. the bad behavior no longer happens with contract checks disabled (`-Zcontract-checks=no`).

The change is made in preparation for adding contract variable declarations - variables declared before the `requires` assertion, and accessible from both `requires` and `ensures`, but not in the function body (PR #144444). As those declarations may also have side-effects, it's good to guard them with `contract_checks` - the new lowering approach allows for this to be done easily.

Contracts tracking issue: #128044

**Known limiatations**:

- It is still possible to early return from the *function* from within a contract, e.g.

  ```rust
  #[ensures({if x > 0 { return 0 }; |_| true})]
  fn foo(x: u32) -> i32 {
      42
  }
  ```

  When `foo` is called with an argument greater than 0, instead of `42`, `0` will be returned.

  As this is not a regression, it is not addressed in this PR. However, it may be worth revisiting later down the line, as users may expect a form of early return from *contract specifications*, and so returning from the entire *function* could cause confusion.

- ~Contracts are still not optimised out when disabled. Currently, even when contracts are disabled, the code generated causes existing optimisations to fail, meaning even disabled contracts could impact runtime performance. This issue is blocking #136578, and has not been addressed in this PR, i.e. the `mir-opt` and `codegen` tests that fail in #136578 still fail with these new HIR lowering changes.~ Contracts should now be optimised out when disabled, however some regressions tests still need to be added to be sure that is indeed the case.
@dawidl022 dawidl022 force-pushed the contracts/variable-scoping-rebased branch from e41951a to 838dda6 Compare October 17, 2025 17:00
@dawidl022 dawidl022 marked this pull request as ready for review October 17, 2025 17:13
@rustbot rustbot added the S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. label Oct 17, 2025
@rustbot rustbot removed the S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. label Oct 17, 2025
@rustbot
Copy link
Collaborator

rustbot commented Oct 17, 2025

r? @jackh726

rustbot has assigned @jackh726.
They will have a look at your PR within the next two weeks and either review your PR or reassign to another reviewer.

Use r? to explicitly pick a reviewer

@dawidl022 dawidl022 force-pushed the contracts/variable-scoping-rebased branch from 838dda6 to a291a4d Compare October 17, 2025 17:14
@rust-log-analyzer

This comment has been minimized.

Contract variables can be declared in the `requires` clause and
can be referenced both in `requires` and `ensures`, subject to usual
borrow checking rules.

This allows any setup common to both the `requires` and `ensures`
clauses to only be done once.
@dawidl022 dawidl022 force-pushed the contracts/variable-scoping-rebased branch from a291a4d to aeae085 Compare October 18, 2025 14:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. T-libs Relevant to the library team, which will review and decide on the PR/issue.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants