Skip to content

Conversation

lmondada
Copy link
Contributor

@lmondada lmondada commented Aug 27, 2025

This PR updates the relrc dependency to v0.5.0 and completely redesigns the CommitStateSpace struct. The result is much cleaner:

  • PersistentHugr is an immutable data type following the design of the types of the im crate, i.e. it is a mutable Rust type that is cheap to clone and mutate (immutability -- if desired -- is achieved by cloning before mutating).
  • Commit is a "transaction", i.e. one atomic mutation that was applied to a PersistentHugr. It has an ID, obtainable through commit.id() and a lifetime parameter that ensures it does not stay in scope beyond the lifetime of the PersistentHugr
  • Each PersistentHugr belongs to a unique CommitStateSpace. A CommitStateSpace is a registry of all commits of the PersistentHugrs within that state space. It keeps the map between commits and IDs.

This is a breaking change, but does not trigger a breaking HUGR release because it is hidden behind an unstable feature.

@lmondada lmondada changed the title feat!: Upgrade to relrc 0.5 feat!(persistent): Upgrade to relrc 0.5 Aug 27, 2025
@lmondada lmondada changed the title feat!(persistent): Upgrade to relrc 0.5 feat!(persistent): Redesign CommitStateSpace, bound Commit lifetime Aug 28, 2025
@lmondada lmondada changed the title feat!(persistent): Redesign CommitStateSpace, bound Commit lifetime feat(persistent)!: Redesign CommitStateSpace, bound Commit lifetime Aug 28, 2025
@lmondada lmondada changed the title feat(persistent)!: Redesign CommitStateSpace, bound Commit lifetime feat(persistent): Redesign CommitStateSpace, bound Commit lifetime Aug 28, 2025
Copy link

codecov bot commented Aug 28, 2025

Codecov Report

❌ Patch coverage is 88.73057% with 87 lines in your changes missing coverage. Please review.
✅ Project coverage is 83.36%. Comparing base (1cd08ef) to head (0408889).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
hugr-persistent/src/persistent_hugr.rs 85.50% 27 Missing and 3 partials ⚠️
hugr-persistent/src/commit.rs 90.21% 16 Missing and 2 partials ⚠️
hugr-persistent/src/state_space.rs 74.07% 12 Missing and 2 partials ⚠️
hugr-persistent/src/commit/boundary.rs 94.64% 5 Missing and 1 partial ⚠️
hugr-persistent/src/walker.rs 86.04% 3 Missing and 3 partials ⚠️
hugr-persistent/src/trait_impls.rs 86.48% 4 Missing and 1 partial ⚠️
hugr-persistent/src/tests.rs 86.95% 3 Missing ⚠️
hugr-persistent/src/persistent_hugr/serial.rs 75.00% 2 Missing ⚠️
hugr-persistent/src/subgraph.rs 90.00% 2 Missing ⚠️
hugr-persistent/src/state_space/serial.rs 97.14% 1 Missing ⚠️
Additional details and impacted files
@@           Coverage Diff           @@
##             main    #2534   +/-   ##
=======================================
  Coverage   83.35%   83.36%           
=======================================
  Files         256      257    +1     
  Lines       50618    50664   +46     
  Branches    46141    46187   +46     
=======================================
+ Hits        42195    42238   +43     
+ Misses       6061     6059    -2     
- Partials     2362     2367    +5     
Flag Coverage Δ
python 91.53% <ø> (ø)
rust 82.57% <88.73%> (+0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@lmondada lmondada requested a review from zrho August 28, 2025 08:14
@lmondada lmondada marked this pull request as ready for review August 31, 2025 12:46
@lmondada lmondada requested a review from a team as a code owner August 31, 2025 12:46
@zrho
Copy link
Contributor

zrho commented Sep 1, 2025

Could you explain what the purpose of the lifetime parameter is in more detail? It looks to me as if the Commit seems to reference the CommitStateSpace and is keeping it alive, so it can't occur that a Commit lives but its CommitStateSpace does not. What is the scenario you want to exclude? Is it simply one where you have Commits that aren't necessary anymore, or is it a safety issue of Commits referencing freed data?

@lmondada
Copy link
Contributor Author

lmondada commented Sep 2, 2025

Of course, sorry that was not clear.

I've expanded the docs of Commit with the following section:

/// # Lifetime of commits
///
/// A commit remains valid as long as the [`CommitStateSpace`] containing it is
/// alive. Note that it is also sufficient that a [`PersistentHugr`] containing
/// the commit is alive, given that the [`CommitStateSpace`] is guaranteed to
/// be alive as long as any of its contained [`PersistentHugr`]s. In other
/// words, the lifetime dependency is:
/// ```ignore
/// PersistentHugr -> CommitStateSpace -> Commit
/// ```
/// where `->` can be read as "is outlived by" (or "maintains a strong reference
/// to"). Note that the dependencies are NOT valid in the other direction: a
/// [`Commit`] only maintains a weak reference to its [`CommitStateSpace`].
///
/// When a [`CommitStateSpace`] goes out of scope, all its commit become
/// invalid. The implementation uses lifetimes to ensure at compile time that
/// the commit is valid throughout its lifetime. All constructors of [`Commit`]
/// thus expect a reference to the state space that the commit should be added
/// to, which fixes the lifetime of the commit.
///
/// Methods that directly modify the lifetime are marked as `unsafe`. It is up
/// to the user to ensure that the commit is valid throughout its updated
/// lifetime.

I hope that clarifies your question!

pub(super) base_commit: CommitId,
#[repr(transparent)]
pub struct CommitStateSpace {
registry: Rc<RefCell<Registry<CommitData, ()>>>,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does the Rc really make sense here? That means you can cheaply clone a CommitStateSpace, but the clones will then share the state hidden inside the RefCell. I would not expect cloning a CommitStateSpace to be something you want to do often....???

(One can imagine a "fork" operation but TBH I'm not really sure why you would want to do that either ;-) unless Ids are scarce or something)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good comment. You are right that cloning could mean two different things here: i) cloning handles to a same state space or ii) creating a new state space that as you say "forks" the state space.

The semantics of cloning here are the former. We need to have multiple handles to the same state space, as every PersistentHugr contains an Rc to the state space it belongs to.

I've considered deleting the Clone implementation (as we often don't want to clone handles ourselves), but there are cases where this is useful, e.g. in testing where we first create PersistentHugr, and then want to create new PersistentHugrs in the same state space as the first.

All in all, I think keeping this as is is the simplest solution. I've added a sentence in the docs specifying the behaviour of cloning.

/// - there is a unique commit of variant [`CommitData::Base`] and its ID
/// is `base_commit_id`.
graph: HistoryGraph<CommitData, ()>,
/// The unique root of the commit graph.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"(Cache of)" perhaps in that it could be computed (by following the parent pointer from any commit, result would be the same regardless of which commit you started from)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed. Have added "cache of" as well as written explicitly the invariant that all paths from a commit in self through ancestors will lead to this root.

/// the same node.
/// - there is a unique commit of variant [`CommitData::Base`] and its ID
/// is `base_commit_id`.
graph: HistoryGraph<CommitData, ()>,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So this is the bit that might be expensive to clone. I'm not terribly clear from the docs of HistoryGraph as to whether it's cheap, or whether it's actually a complete+owned petgraph constructed from (a cache of) the parent pointers of some set of commits. It might be worth nothing that a persistent (im::) list/set of "leaf commits" would store the same information (perhaps in less accessible form) and really would be cheap to clone...

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, it's not that cheap to clone, so a persistent list (inside HistoryGraph) would be better - but storing the complete list of commits (as it does, rather than just leaves) might be best because then clones would only ever append to the list rather than removing nodes that are no longer leaves.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes agreed; I will create an issue for this, to be picked up when this becomes a bottleneck.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@lmondada lmondada requested a review from acl-cqc September 30, 2025 13:16
@lmondada lmondada requested a review from aborgna-q October 7, 2025 22:48
@lmondada lmondada enabled auto-merge October 12, 2025 01:27
@lmondada lmondada added this pull request to the merge queue Oct 12, 2025
Merged via the queue into main with commit ac418b5 Oct 12, 2025
26 checks passed
@lmondada lmondada deleted the lm/phugr-relrc5 branch October 12, 2025 01:41
@hugrbot hugrbot mentioned this pull request Oct 10, 2025
github-merge-queue bot pushed a commit that referenced this pull request Oct 13, 2025
## 🤖 New release

* `hugr-model`: 0.23.0 -> 0.24.0
* `hugr-core`: 0.23.0 -> 0.24.0 (✓ API compatible changes)
* `hugr-llvm`: 0.23.0 -> 0.24.0 (✓ API compatible changes)
* `hugr-passes`: 0.23.0 -> 0.24.0 (✓ API compatible changes)
* `hugr-persistent`: 0.3.0 -> 0.3.1 (✓ API compatible changes)
* `hugr`: 0.23.0 -> 0.24.0 (✓ API compatible changes)
* `hugr-cli`: 0.23.0 -> 0.24.0 (✓ API compatible changes)

<details><summary><i><b>Changelog</b></i></summary><p>

## `hugr-model`

<blockquote>

##
[0.23.0](hugr-model-v0.22.4...hugr-model-v0.23.0)
- 2025-09-30

### Bug Fixes

- [**breaking**] Appease `cargo-audit` by replacing unmaintained
dependencies ([#2572](#2572))

### New Features

- Documentation and error hints
([#2523](#2523))
</blockquote>

## `hugr-core`

<blockquote>

##
[0.24.0](hugr-core-v0.23.0...hugr-core-v0.24.0)
- 2025-10-13

### Bug Fixes

- Preserve offset for CFG edges when serializing to JSON
([#2606](#2606))

### New Features

- LLVM lowering for borrow arrays using bitmasks
([#2574](#2574))
- *(py, core, llvm)* add `is_borrowed` op for BorrowArray
([#2610](#2610))

### Refactor

- [**breaking**] consistent inout order in borrow array
([#2621](#2621))
</blockquote>

## `hugr-llvm`

<blockquote>

##
[0.24.0](hugr-llvm-v0.23.0...hugr-llvm-v0.24.0)
- 2025-10-13

### New Features

- LLVM lowering for borrow arrays using bitmasks
([#2574](#2574))
- *(py, core, llvm)* add `is_borrowed` op for BorrowArray
([#2610](#2610))

### Refactor

- [**breaking**] consistent inout order in borrow array
([#2621](#2621))
</blockquote>

## `hugr-passes`

<blockquote>

##
[0.24.0](hugr-passes-v0.23.0...hugr-passes-v0.24.0)
- 2025-10-13

### New Features

- Add handler for copying / discarding borrow arrays to default
lineariser ([#2602](#2602))
</blockquote>

## `hugr-persistent`

<blockquote>

##
[0.3.1](hugr-persistent-v0.3.0...hugr-persistent-v0.3.1)
- 2025-10-13

### Bug Fixes

- *(test)* No extension serialisation in persistent-hugr testing
([#2612](#2612))

### New Features

- *(persistent)* Redesign CommitStateSpace, bound Commit lifetime
([#2534](#2534))
</blockquote>

## `hugr`

<blockquote>

##
[0.24.0](hugr-v0.23.0...hugr-v0.24.0)
- 2025-10-13

### Bug Fixes

- Preserve offset for CFG edges when serializing to JSON
([#2606](#2606))

### New Features

- Add handler for copying / discarding borrow arrays to default
lineariser ([#2602](#2602))
- LLVM lowering for borrow arrays using bitmasks
([#2574](#2574))
- *(py, core, llvm)* add `is_borrowed` op for BorrowArray
([#2610](#2610))

### Refactor

- [**breaking**] consistent inout order in borrow array
([#2621](#2621))
</blockquote>

## `hugr-cli`

<blockquote>

##
[0.24.0](hugr-cli-v0.23.0...hugr-cli-v0.24.0)
- 2025-10-13

### Documentation

- update cli readme ([#2601](#2601))
</blockquote>


</p></details>

---
This PR was generated with
[release-plz](https://github.com/release-plz/release-plz/).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants