#reference #lifetime #deferred #borrow #pointers #raw-pointers

no-std deferred-reference

A deferred reference is not an actual reference, it is merely a smart pointer tied to the lifetime of the location it points to

2 releases

0.1.2 Apr 5, 2021
0.1.1 Apr 4, 2021
0.1.0 Apr 3, 2021

#402 in Concurrency

MIT license

140KB
1.5K SLoC

Deferred Reference

This crate helps with creating multiple mutable references to the contents of a variable without triggering undefined behavior. The Rust borrow rules dictate that it is undefined behavior to create more than one mutable reference to the same region, even if the mutable reference is not used. However, this can sometimes be a tad too restrictive if the programmer knows that two mutable references will not overlap. Using raw pointers, it is already possible to work-around the Rust borrow rules today, but this requires wizard-like skills and in-depth knowledge of handling raw pointers and this is more prone to error than using Rust references. With the introduction of non-lexical lifetimes in the Rust 2018 edition, the ergonomics around references have already significantly improved, but there are still some corner-cases where the programmer wished there was some way to create non-overlapping mutable references into the same location (e.g. disjoint indices of a slice or array), without resorting to manually managed raw pointers. In order to aid with this, this crate introduces the concept of a "deferred reference"1. A deferred reference is almost exactly like a regular reference (e.g. a &T or a &mut T), but it differs from a regular reference in the following ways:

  • A deferred reference is not an actual reference, it is merely a smart pointer tied to the lifetime of the location it points to (regular raw pointers always have a static lifetime, and can thus become dangling if the location it points to is moved or dropped).
  • It is allowed to keep multiple deferred mutable references around (as long as these are not dereferenced in a way so that these create an overlap between a mutable reference and another (de)reference).

Getting started

If you're on nightly Rust, add the following dependency to your Cargo.toml:

[dependencies]
deferred-reference = { version = "0.1.2" }

This crate uses some unstable features, but it also works on stable Rust with less features. For using this crate in stable Rust you need to disable the unstable nightly features using the default-features flag, like so:

[dependencies]
deferred-reference = { version = "0.1.2", default-features = false }

Please see the documentation for this crate on how to get started with some concrete code examples.

#![no_std] environments

This crate is entirely #![no_std] and does not depend on the alloc crate. No additional Cargo.toml features need to be configured in order to support #![no_std] environments. This crate also does not have any dependencies in its Cargo.toml.

Miri tested

This crate is extensively tested using Miri using the -Zmiri-track-raw-pointers flag:

$ MIRIFLAGS="-Zmiri-track-raw-pointers" cargo miri test

Miri follows the Stacked Borrows model (by Ralf Jung et al.) and so does this crate. If you happen to spot any violations of this model in this crate, feel free to open a Github issue!

License

This project is licensed under the MIT license. Please see the file LICENSE.md in the root of this project for the full license text.

Contributing

It is encouraged to open a Github issue if you run into problems or if you have a feature request. At this point, this project does not accept pull requests yet. Please check back later!

Footnotes

[1]: The concept of "deferred references" is inspired by the concept of "Deferred Borrows" authored by Chris Fallin. However, these are not entirely the same concept. These differ in the sense that deferred borrows bring an extension to the Rust type system (called static path-dependent types) and its implementation is intended to live within the Rust compiler, while deferred references are implemented in Rust code that is already possible today with its existing type system. The trade-off made here is that this requires minimal use of unsafe code blocks with deferred references, while deferred borrows would work entirely within "safe Rust" if these were to be implemented in the Rust compiler. There are also some similarities between the two concepts: both concepts are statically applied during compile-time and due not incur any runtime overhead. Also, with both approaches an actual reference is not created until the reference is actually in use (i.e. dereferenced or borrowed for an extended period of time).

No runtime deps

Features