Simplified Borrow Checker Implementation in Rust
This challenge asks you to implement a simplified version of Rust's borrow checker. While a full implementation is incredibly complex, this exercise focuses on core concepts like tracking mutable and immutable borrows and enforcing the rules that prevent data races and dangling pointers. This is a valuable exercise for understanding Rust's memory safety guarantees.
Problem Description
You are to create a module BorrowTracker that simulates a simplified borrow checker. The module should provide functions to:
borrow_immutable(ref: &T): Takes an immutable reference&Tand records it. The tracker should ensure that no mutable borrows exist while an immutable borrow is active.borrow_mutable(ref: &mut T): Takes a mutable reference&mut Tand records it. The tracker should ensure that no other borrows (mutable or immutable) exist while a mutable borrow is active.release_borrow(ref: &T): Releases a previously recorded borrow. Therefmust match a currently active borrow.is_borrowed(ref: &T) -> bool: Checks if a given reference is currently borrowed.
The BorrowTracker should maintain an internal data structure (e.g., a HashMap) to track active borrows. The challenge is to implement the logic to enforce Rust's borrowing rules:
- Immutable Borrows: Multiple immutable borrows can exist simultaneously.
- Mutable Borrows: Only one mutable borrow can exist at a time.
- Mutable and Immutable Borrows: A mutable borrow cannot exist while any immutable borrows exist.
- Release: Releasing a borrow that doesn't exist or releasing the wrong borrow should result in a panic.
Examples
Example 1:
Input:
let mut data = 10;
let ref1 = &data;
let ref2 = &data;
let ref3 = &mut data;
Output:
(Success)
Explanation:
Initially, no borrows exist. `ref1` and `ref2` are immutable borrows, which are allowed. `ref3` is a mutable borrow, which is allowed because no immutable borrows are active.
Example 2:
Input:
let mut data = 10;
let ref1 = &data;
let ref2 = &mut data;
Output:
(Panic)
Explanation:
`ref1` is an immutable borrow. Attempting to create `ref2` (a mutable borrow) while `ref1` is active violates the borrowing rules, resulting in a panic.
Example 3:
Input:
let mut data = 10;
let ref1 = &mut data;
let ref2 = &data;
Output:
(Panic)
Explanation:
`ref1` is a mutable borrow. Attempting to create `ref2` (an immutable borrow) while `ref1` is active violates the borrowing rules, resulting in a panic.
Example 4:
Input:
let mut data = 10;
let ref1 = &data;
drop(ref1);
let ref2 = &mut data;
Output:
(Success)
Explanation:
`ref1` is an immutable borrow. After `ref1` is released, a mutable borrow `ref2` is allowed.
Constraints
- The
BorrowTrackershould be thread-safe (usingMutexor similar synchronization primitives). While the challenge doesn't require concurrent access in the test cases, the design should accommodate it. - The
refparameter inborrow_immutable,borrow_mutable, andrelease_borrowshould be used to identify the borrow. A simple pointer comparison is sufficient for this simplified implementation. - Panics should be used to indicate borrowing rule violations.
- The implementation should be reasonably efficient. Avoid unnecessary allocations or complex data structures.
Notes
- This is a simplified borrow checker. It does not handle all the complexities of the real Rust borrow checker (e.g., lifetimes, regions, interior mutability).
- Focus on correctly implementing the core borrowing rules.
- Consider using a
HashMapto store active borrows, where the key is the reference and the value is a boolean indicating whether the borrow is active. - Think carefully about the order of operations and how to ensure that borrows are released correctly.
- The
dropfunction is not directly part of the challenge, but it's important to understand how it relates to releasing borrows. You can simulatedropby simply removing the borrow from the tracker. - Error messages in the panic should be descriptive enough to indicate the nature of the borrowing violation.